Database Migrations logo

Database Migrations

CommunityPopular
affaan-m
database-migrations

Buenas prácticas de migración de base de datos para cambios de esquema, migraciones de datos, rollbacks y despliegues de tiempo cero en PostgreSQL, MySQL y ORMs comunes (Prisma, Drizzle, Kysely, Django, TypeORM, golang-migrate).

Overview

Publisheraffaan-m
RepositoryECC
Skill namedatabase-migrations
Stars
261.1K
Forks
39.1K
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by affaan-m on GitHub. Read the source before you install it.

Installation

Install the Database Migrations AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/affaan-m/ECC.git /tmp/ECC
mkdir -p .claude/skills
cp -r /tmp/ECC/docs/es/skills/database-migrations .claude/skills/database-migrations
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Database Migrations in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Database Migrations on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Database Migrations is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Patrones de Migración de Base de Datos

Cambios de esquema de base de datos seguros y reversibles para sistemas de producción.

Cuándo Activar

  • Crear o alterar tablas de base de datos
  • Agregar/eliminar columnas o índices
  • Ejecutar migraciones de datos (backfill, transformación)
  • Planificar cambios de esquema de tiempo cero (zero-downtime)
  • Configurar herramientas de migración para un nuevo proyecto

Principios Fundamentales

  1. Cada cambio es una migración — nunca alterar bases de datos de producción manualmente
  2. Las migraciones son solo hacia adelante en producción — los rollbacks usan nuevas migraciones hacia adelante
  3. Las migraciones de esquema y de datos son separadas — nunca mezclar DDL y DML en una migración
  4. Probar migraciones contra datos de tamaño de producción — una migración que funciona en 100 filas puede bloquear en 10M
  5. Las migraciones son inmutables una vez desplegadas — nunca editar una migración que ya se ejecutó en producción

Lista de Verificación de Seguridad de Migración

Antes de aplicar cualquier migración:

  • La migración tiene tanto UP como DOWN (o está marcada explícitamente como irreversible)
  • Sin bloqueos de tabla completa en tablas grandes (usar operaciones concurrentes)
  • Las nuevas columnas tienen valores predeterminados o son nullable (nunca agregar NOT NULL sin valor predeterminado)
  • Índices creados de forma concurrente (no en línea con CREATE TABLE para tablas existentes)
  • El backfill de datos es una migración separada del cambio de esquema
  • Probado contra una copia de datos de producción
  • Plan de rollback documentado

Patrones PostgreSQL

Agregar una Columna de Forma Segura

sql
-- BIEN: Columna nullable, sin bloqueo
ALTER TABLE users ADD COLUMN avatar_url TEXT;

-- BIEN: Columna con valor predeterminado (Postgres 11+ es instantáneo, sin reescritura)
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;

-- MAL: NOT NULL sin valor predeterminado en tabla existente (requiere reescritura completa)
ALTER TABLE users ADD COLUMN role TEXT NOT NULL;
-- Esto bloquea la tabla y reescribe cada fila

Agregar un Índice Sin Tiempo de Inactividad

sql
-- MAL: Bloquea escrituras en tablas grandes
CREATE INDEX idx_users_email ON users (email);

-- BIEN: No bloqueante, permite escrituras concurrentes
CREATE INDEX CONCURRENTLY idx_users_email ON users (email);

-- Nota: CONCURRENTLY no puede ejecutarse dentro de un bloque de transacción
-- La mayoría de herramientas de migración necesitan manejo especial para esto

Renombrar una Columna (Zero-Downtime)

Nunca renombrar directamente en producción. Usar el patrón expand-contract:

sql
-- Paso 1: Agregar nueva columna (migración 001)
ALTER TABLE users ADD COLUMN display_name TEXT;

-- Paso 2: Backfill de datos (migración 002, migración de datos)
UPDATE users SET display_name = username WHERE display_name IS NULL;

-- Paso 3: Actualizar el código de la aplicación para leer/escribir ambas columnas
-- Desplegar cambios de aplicación

-- Paso 4: Dejar de escribir en la columna antigua, eliminarla (migración 003)
ALTER TABLE users DROP COLUMN username;

Eliminar una Columna de Forma Segura

sql
-- Paso 1: Eliminar todas las referencias de la aplicación a la columna
-- Paso 2: Desplegar la aplicación sin la referencia a la columna
-- Paso 3: Eliminar la columna en la próxima migración
ALTER TABLE orders DROP COLUMN legacy_status;

-- Para Django: usar SeparateDatabaseAndState para eliminar del modelo
-- sin generar DROP COLUMN (luego eliminar en la próxima migración)

Migraciones de Datos Grandes

sql
-- MAL: Actualiza todas las filas en una transacción (bloquea la tabla)
UPDATE users SET normalized_email = LOWER(email);

-- BIEN: Actualización en lotes con progreso
DO $$
DECLARE
  batch_size INT := 10000;
  rows_updated INT;
BEGIN
  LOOP
    UPDATE users
    SET normalized_email = LOWER(email)
    WHERE id IN (
      SELECT id FROM users
      WHERE normalized_email IS NULL
      LIMIT batch_size
      FOR UPDATE SKIP LOCKED
    );
    GET DIAGNOSTICS rows_updated = ROW_COUNT;
    RAISE NOTICE 'Updated % rows', rows_updated;
    EXIT WHEN rows_updated = 0;
    COMMIT;
  END LOOP;
END $$;

Prisma (TypeScript/Node.js)

Flujo de Trabajo

bash
# Crear migración a partir de cambios de esquema
npx prisma migrate dev --name add_user_avatar

# Aplicar migraciones pendientes en producción
npx prisma migrate deploy

# Resetear base de datos (solo desarrollo)
npx prisma migrate reset

# Generar cliente después de cambios de esquema
npx prisma generate

Ejemplo de Esquema

prisma
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  avatarUrl String?  @map("avatar_url")
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
  orders    Order[]

  @@map("users")
  @@index([email])
}

Migración SQL Personalizada

Para operaciones que Prisma no puede expresar (índices concurrentes, backfills de datos):

bash
# Crear migración vacía, luego editar el SQL manualmente
npx prisma migrate dev --create-only --name add_email_index
sql
-- migrations/20240115_add_email_index/migration.sql
-- Prisma no puede generar CONCURRENTLY, por lo que se escribe manualmente
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users (email);

Drizzle (TypeScript/Node.js)

Flujo de Trabajo

bash
# Generar migración a partir de cambios de esquema
npx drizzle-kit generate

# Aplicar migraciones
npx drizzle-kit migrate

# Hacer push del esquema directamente (solo desarrollo, sin archivo de migración)
npx drizzle-kit push

Ejemplo de Esquema

typescript
import { pgTable, text, timestamp, uuid, boolean } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id: uuid("id").primaryKey().defaultRandom(),
  email: text("email").notNull().unique(),
  name: text("name"),
  isActive: boolean("is_active").notNull().default(true),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

Kysely (TypeScript/Node.js)

Flujo de Trabajo (kysely-ctl)

bash
# Inicializar archivo de configuración (kysely.config.ts)
kysely init

# Crear un nuevo archivo de migración
kysely migrate make add_user_avatar

# Aplicar todas las migraciones pendientes
kysely migrate latest

# Revertir la última migración
kysely migrate down

# Mostrar estado de migraciones
kysely migrate list

Archivo de Migración

typescript
// migrations/2024_01_15_001_create_user_profile.ts
import { type Kysely, sql } from 'kysely'

// IMPORTANTE: Siempre usar Kysely<any>, no tu interfaz de DB tipada.
// Las migraciones están congeladas en el tiempo y no deben depender de los tipos de esquema actuales.
export async function up(db: Kysely<any>): Promise<void> {
  await db.schema
    .createTable('user_profile')
    .addColumn('id', 'serial', (col) => col.primaryKey())
    .addColumn('email', 'varchar(255)', (col) => col.notNull().unique())
    .addColumn('avatar_url', 'text')
    .addColumn('created_at', 'timestamp', (col) =>
      col.defaultTo(sql`now()`).notNull()
    )
    .execute()

  await db.schema
    .createIndex('idx_user_profile_avatar')
    .on('user_profile')
    .column('avatar_url')
    .execute()
}

export async function down(db: Kysely<any>): Promise<void> {
  await db.schema.dropTable('user_profile').execute()
}

Migrador Programático

typescript
import { Migrator, FileMigrationProvider } from 'kysely'
import { promises as fs } from 'fs'
import * as path from 'path'
// Solo ESM — CJS puede usar __dirname directamente
import { fileURLToPath } from 'url'
const migrationFolder = path.join(
  path.dirname(fileURLToPath(import.meta.url)),
  './migrations',
)

// `db` es tu instancia de base de datos Kysely<any>
const migrator = new Migrator({
  db,
  provider: new FileMigrationProvider({
    fs,
    path,
    migrationFolder,
  }),
  // ADVERTENCIA: Solo habilitar en desarrollo. Deshabilita la validación de
  // ordenamiento por timestamp, lo que puede causar deriva de esquema entre entornos.
  // allowUnorderedMigrations: true,
})

const { error, results } = await migrator.migrateToLatest()

results?.forEach((it) => {
  if (it.status === 'Success') {
    console.log(`migration "${it.migrationName}" executed successfully`)
  } else if (it.status === 'Error') {
    console.error(`failed to execute migration "${it.migrationName}"`)
  }
})

if (error) {
  console.error('migration failed', error)
  process.exit(1)
}

Django (Python)

Flujo de Trabajo

bash
# Generar migración a partir de cambios de modelo
python manage.py makemigrations

# Aplicar migraciones
python manage.py migrate

# Mostrar estado de migraciones
python manage.py showmigrations

# Generar migración vacía para SQL personalizado
python manage.py makemigrations --empty app_name -n description

Migración de Datos

python
from django.db import migrations

def backfill_display_names(apps, schema_editor):
    User = apps.get_model("accounts", "User")
    batch_size = 5000
    users = User.objects.filter(display_name="")
    while users.exists():
        batch = list(users[:batch_size])
        for user in batch:
            user.display_name = user.username
        User.objects.bulk_update(batch, ["display_name"], batch_size=batch_size)

def reverse_backfill(apps, schema_editor):
    pass  # Migración de datos, no se necesita reversión

class Migration(migrations.Migration):
    dependencies = [("accounts", "0015_add_display_name")]

    operations = [
        migrations.RunPython(backfill_display_names, reverse_backfill),
    ]

SeparateDatabaseAndState

Eliminar una columna del modelo Django sin eliminarla de la base de datos inmediatamente:

python
class Migration(migrations.Migration):
    operations = [
        migrations.SeparateDatabaseAndState(
            state_operations=[
                migrations.RemoveField(model_name="user", name="legacy_field"),
            ],
            database_operations=[],  # No tocar la DB todavía
        ),
    ]

golang-migrate (Go)

Flujo de Trabajo

bash
# Crear par de migración
migrate create -ext sql -dir migrations -seq add_user_avatar

# Aplicar todas las migraciones pendientes
migrate -path migrations -database "$DATABASE_URL" up

# Revertir la última migración
migrate -path migrations -database "$DATABASE_URL" down 1

# Forzar versión (corregir estado sucio)
migrate -path migrations -database "$DATABASE_URL" force VERSION

Archivos de Migración

sql
-- migrations/000003_add_user_avatar.up.sql
ALTER TABLE users ADD COLUMN avatar_url TEXT;
CREATE INDEX CONCURRENTLY idx_users_avatar ON users (avatar_url) WHERE avatar_url IS NOT NULL;

-- migrations/000003_add_user_avatar.down.sql
DROP INDEX IF EXISTS idx_users_avatar;
ALTER TABLE users DROP COLUMN IF EXISTS avatar_url;

Estrategia de Migración de Zero-Downtime

Para cambios críticos de producción, seguir el patrón expand-contract:

Fase 1: EXPAND (Expandir)
  - Agregar nueva columna/tabla (nullable o con valor predeterminado)
  - Desplegar: la app escribe en AMBAS, vieja y nueva
  - Backfill de datos existentes

Fase 2: MIGRATE (Migrar)
  - Desplegar: la app lee de la NUEVA, escribe en AMBAS
  - Verificar consistencia de datos

Fase 3: CONTRACT (Contraer)
  - Desplegar: la app solo usa la NUEVA
  - Eliminar columna/tabla antigua en migración separada

Ejemplo de Línea de Tiempo

Día 1: Migración agrega columna new_status (nullable)
Día 1: Desplegar app v2 — escribe en status y new_status
Día 2: Ejecutar migración de backfill para filas existentes
Día 3: Desplegar app v3 — lee solo de new_status
Día 7: Migración elimina columna status antigua

Anti-Patrones

Anti-PatrónPor Qué FallaMejor Enfoque
SQL manual en producciónSin historial de auditoría, no repetibleSiempre usar archivos de migración
Editar migraciones desplegadasCausa deriva entre entornosCrear nueva migración en su lugar
NOT NULL sin valor predeterminadoBloquea tabla, reescribe todas las filasAgregar nullable, backfill, luego agregar restricción
Índice en línea en tabla grandeBloquea escrituras durante la construcciónCREATE INDEX CONCURRENTLY
Esquema + datos en una migraciónDifícil de revertir, transacciones largasMigraciones separadas
Eliminar columna antes de eliminar códigoErrores de aplicación por columna faltanteEliminar código primero, eliminar columna en el próximo despliegue

Frequently asked questions

What does the Database Migrations AI skill do?

Buenas prácticas de migración de base de datos para cambios de esquema, migraciones de datos, rollbacks y despliegues de tiempo cero en PostgreSQL, MySQL y ORMs comunes (Prisma, Drizzle, Kysely, Django, TypeORM, golang-migrate).

Why use Database Migrations on TypingMind?

Because you install it once and use it with any model. Database Migrations is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Database Migrations in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/affaan-m/ECC/tree/main/docs/es/skills/database-migrations. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Database Migrations?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Database Migrations?

As many as you like. As long as a model supports skills, you can use Database Migrations with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Database Migrations AI skill free?

Yes. It is published on GitHub by affaan-m under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇