---
name: dbml-experto
description: >
  Experto en DBML (Database Markup Language) — lenguaje declarativo para describir
  esquemas de bases de datos. Cubre sintaxis (tables, refs, enums, indexes, notes,
  projects), parseo programático con @dbml/core cuando está instalado, fallback a
  heurísticas de texto plano cuando no, y casos de uso en proyectos data-heavy:
  análisis de schema, migración entre dialectos (PG/MySQL/MSSQL), detección de
  relaciones huérfanas, generación de DDL. Cargar cuando un proyecto contenga
  archivos .dbml, o cuando el usuario mencione dbdiagram.io, chartdb.io, schema
  as code o DBML explícitamente. NO cargar para SQL plano, Prisma schema, TypeORM
  entities u otros DSLs de schema — son otros skills distintos.
version: "1.0.0"
herramientasPermitidas: [Read]
evolvable: true  # default para skill estandar
exclusiones:
  - "No cargar para SQL plano (DDL, DML, migraciones) — para SQL cargar `postgresql-experto` o `sql-optimizacion`."
  - "No cargar para Prisma schema (.prisma), TypeORM entities o SQLAlchemy declarative — son DSLs incompatibles con DBML."
  - "No cargar para generar diagramas visuales de arquitectura — para diagramas cargar `diagrama-arquitectura` o `generacion-mermaid`."
  - "No cargar para aplicar migraciones a una base de datos real — este skill es análisis estático de DBML, no ejecución de DDL."
---
# DBML Experto — Parseo y análisis de esquemas DBML

## Cuándo cargar esta skill

- El proyecto contiene archivos `.dbml` o un directorio `db/` con `.dbml`.
- El usuario menciona `dbdiagram.io`, `chartdb.io`, "schema as code", "DBML",
  o pide migrar entre dialectos SQL.
- Un agente `datos-swl` o revisor de lenguaje necesita analizar relaciones
  programáticamente y el proyecto usa DBML como fuente de verdad del schema.

**No cargar** cuando:
- El schema está en SQL plano (usar skills de SQL/backend-* correspondientes).
- El schema está en Prisma (`.prisma`), TypeORM, SQLAlchemy declarative — son otros DSLs.
- La tarea es solo leer un diagrama sin modificar ni analizar relaciones.

---

## Dependencia opcional — `@dbml/core`

Esta skill **funciona sin dependencias** usando heurísticas de texto plano, pero
gana precisión si el proyecto del usuario tiene `@dbml/core` instalado.

### Detectar si está disponible

```bash
# En el proyecto del usuario
test -d node_modules/@dbml/core && echo "DISPONIBLE" || echo "NO_INSTALADO"
```

### Si NO está disponible y la tarea requiere parseo estructural

Sugerir al usuario instalarlo en **su** proyecto (nunca en swl-ses):

```bash
npm install --save-dev @dbml/core
```

Documentado también en `MANUAL_USO.md` → "Dependencias externas — referencia
detallada" → `@dbml/core`.

### Si SÍ está disponible — patrón de uso

```javascript
const fs = require('node:fs');
const { importer } = require('@dbml/core');

const texto = fs.readFileSync('db/schema.dbml', 'utf8');
const schema = importer.import(texto, 'dbml');

// schema.tables[i]
//   .name, .note, .fields[], .indexes[]
// schema.refs[i]
//   .endpoints[0].tableName, .endpoints[0].fieldNames
//   .endpoints[1].tableName, .endpoints[1].fieldNames
//   .onDelete, .onUpdate
// schema.enums[i]
//   .name, .values[]
```

---

## Sintaxis DBML esencial

### Tabla básica

```dbml
Table users {
  id int [pk, increment]
  email varchar [unique, not null]
  name varchar
  created_at timestamp [default: `now()`]
}
```

| Modificador | Significado |
|-------------|-------------|
| `pk` | Primary key |
| `increment` | Auto-incremento (serial/identity) |
| `unique` | Restricción UNIQUE |
| `not null` | NOT NULL |
| `default: X` | Valor default (usar backticks para expresiones) |
| `note: 'texto'` | Comentario asociado al campo |
| `ref: > tabla.campo` | Relación inline |

### Relaciones (refs)

Tres formas equivalentes:

```dbml
# A. Inline en el campo
Table posts {
  id int [pk]
  user_id int [ref: > users.id]   // many-to-one
}

# B. Referencia separada
Ref: posts.user_id > users.id

# C. Bloque de referencias
Ref user_post {
  users.id < posts.user_id [delete: cascade, update: no action]
}
```

Cardinalidades:
- `>` many-to-one (de hijo hacia padre)
- `<` one-to-many
- `-` one-to-one
- `<>` many-to-many (requiere tabla pivote explícita)

### Índices

```dbml
Table users {
  id int [pk]
  email varchar
  country varchar

  Indexes {
    email [unique]
    (country, email) [name: 'idx_country_email']
    `lower(email)` [type: btree]
  }
}
```

### Enums

```dbml
Enum order_status {
  pending
  paid
  shipped [note: 'el producto salió del almacén']
  cancelled
}

Table orders {
  id int [pk]
  status order_status [not null, default: 'pending']
}
```

### Agrupación en Projects y TableGroups

```dbml
Project mi_sistema {
  database_type: 'PostgreSQL'
  Note: 'Esquema principal de la aplicación'
}

TableGroup facturacion {
  orders
  order_items
  payments
}
```

---

## Casos de uso en SWL

### 1. Análisis de relaciones huérfanas

Detectar refs cuyo endpoint apunta a tablas/campos inexistentes:

```javascript
const tablas = new Map(schema.tables.map(t => [t.name, new Set(t.fields.map(f => f.name))]));

const huérfanas = schema.refs.filter(r => {
  for (const ep of r.endpoints) {
    const cols = tablas.get(ep.tableName);
    if (!cols) return true;
    for (const fn of ep.fieldNames) if (!cols.has(fn)) return true;
  }
  return false;
});
```

### 2. Detección de tablas sin PK

```javascript
const sinPK = schema.tables.filter(t =>
  !t.fields.some(f => f.pk) &&
  !t.indexes?.some(i => i.pk)
);
```

Reportar como anti-patrón (excepto tablas de log append-only declaradas en note).

### 3. Migración entre dialectos

Convertir tipos DBML → dialecto específico. Tabla base (extender según necesidad):

| DBML | PostgreSQL | MySQL | MSSQL | SQLite |
|------|-----------|-------|-------|--------|
| `int` | `integer` | `int` | `int` | `integer` |
| `bigint` | `bigint` | `bigint` | `bigint` | `integer` |
| `varchar` | `varchar(255)` | `varchar(255)` | `nvarchar(255)` | `text` |
| `text` | `text` | `text` | `nvarchar(max)` | `text` |
| `timestamp` | `timestamp` | `datetime` | `datetime2` | `text` |
| `boolean` | `boolean` | `tinyint(1)` | `bit` | `integer` |
| `uuid` | `uuid` | `char(36)` | `uniqueidentifier` | `text` |
| `json` | `jsonb` | `json` | `nvarchar(max)` | `text` |

Reglas al migrar:
- `timestamp with tz` → `timestamptz` (PG) / `datetime` (MySQL sin tz) / `datetimeoffset` (MSSQL).
- Defaults con funciones (`` `now()` ``) requieren ajuste por dialecto.
- `increment` → `SERIAL`/`IDENTITY`/`AUTOINCREMENT` según dialecto.
- Reglas `on delete cascade` se soportan en todos; `on delete set null` requiere columna nullable.

### 4. Fallback sin `@dbml/core` — heurísticas de texto

Si el usuario no puede instalar la dependencia, usar regex conservadoras:

```javascript
// Extraer nombres de tablas
const tablas = [...texto.matchAll(/^\s*Table\s+(\w+)\s*\{/gm)].map(m => m[1]);

// Extraer refs inline
const refsInline = [...texto.matchAll(/\[ref:\s*[<>\-]\s*(\w+)\.(\w+)/g)]
  .map(m => ({ tabla: m[1], campo: m[2] }));

// Extraer refs separadas
const refsSep = [...texto.matchAll(/^\s*Ref(?:\s+\w+)?\s*:?\s*(\w+)\.(\w+)\s*([<>\-]+)\s*(\w+)\.(\w+)/gm)]
  .map(m => ({ fromTabla: m[1], fromCampo: m[2], card: m[3], toTabla: m[4], toCampo: m[5] }));
```

**Limitaciones del fallback**:
- No resuelve tipos compuestos ni enums referenciados.
- Puede fallar con comentarios embebidos o strings con llaves.
- No valida sintaxis.

Siempre reportar al usuario: "Análisis hecho sin `@dbml/core` — precisión
limitada, considera instalarlo si vas a tomar decisiones estructurales."

---

## Patrones buenos y malos en DBML

### BIEN — nombres consistentes

```dbml
Table users {
  id int [pk, increment]
  organization_id int [ref: > organizations.id]
}
```

### MAL — inconsistencia snake_case vs camelCase

```dbml
Table users {
  id int [pk]
  organizationId int [ref: > organizations.id]  // mezclar convenciones rompe tooling
}
```

### BIEN — documentar decisiones con `Note`

```dbml
Table events {
  id bigint [pk, increment]
  payload jsonb [not null]

  Note: 'Append-only. No usar UPDATE. Particionada por mes vía pg_partman.'
}
```

### MAL — relaciones implícitas sin `Ref`

```dbml
Table orders {
  id int [pk]
  user_id int  // sin [ref] ni Ref separada: relación invisible al parser
}
```

---

## Integración con otros skills y agentes

- **`datos-swl`** (agente): invoca esta skill al detectar `.dbml` en el proyecto.
- **`revisor-impacto`** (`/swl:revisar-impacto`): puede cruzar refs DBML con el
  grafo de código para detectar tablas sin consumidores.
- **`migrador-swl`** (agente): usa la tabla de migración de tipos para generar
  scripts DDL multi-dialecto.
- **`seguridad.md`** (regla): aplicar revisión de nombres sensibles en columnas
  (email, password, ssn, etc.) independientemente del DSL.

---

## Lo que esta skill NO hace

- **No genera diagramas visuales** — para eso usar `dbdiagram.io`, `chartdb.io` o
  el skill `generacion-mermaid` con output ER.
- **No ejecuta migraciones** — solo analiza y genera SQL como texto. El usuario
  aplica manualmente con su herramienta de migración.
- **No valida contra una BD real** — es análisis estático del DBML.
- **No reemplaza** Prisma/TypeORM/SQLAlchemy — esos son ORMs, DBML es solo el
  schema declarativo.

---

## Gotchas / Errores comunes no obvios

**Las heurísticas de texto plano (sin `@dbml/core`) fallan al parsear tablas con `Note` multilínea**: el regex `^\s*Table\s+(\w+)\s*\{` no coincide cuando la tabla tiene un bloque `Note: '''...'''` antes de los campos, porque la llave de apertura `{` está en la misma línea que `Note`. Causa: DBML permite bloques de texto multilínea con comillas triples que pueden contener llaves. Fix: al usar heurísticas de fallback, reportar explícitamente al usuario que el análisis puede ser incompleto si el schema usa notas multilínea. Para schemas complejos, insistir en instalar `@dbml/core`.

**Las cardinalidades `<>` (many-to-many) en DBML no generan tabla pivote automáticamente**: definir `Ref: users.id <> posts.id` es válido en DBML pero no corresponde a ninguna implementación real de muchos a muchos en SQL — las BDs relacionales requieren tabla intermedia explícita. Causa: DBML permite la cardinalidad `<>` como notación conceptual, pero los parsers de DDL la ignoran o la rechazan. Fix: para relaciones many-to-many, siempre crear la tabla pivote explícita (`user_posts { user_id int [ref: > users.id], post_id int [ref: > posts.id] }`) en lugar de usar `<>` directo.

**`@dbml/core` lanza error de parseo en schemas con nombres de tabla que contienen guiones**: `Table mi-tabla { ... }` es inválido en DBML — los identificadores solo permiten letras, números y guión bajo. Causa: DBML sigue convenciones de identificadores SQL donde el guión es un operador de resta, no parte del nombre. Fix: usar snake_case (`mi_tabla`) o PascalCase (`MiTabla`) para nombres de tablas. Si el schema existente tiene guiones, renombrar antes de parsear con `@dbml/core`.

**El fallback de heurísticas extrae refs inline pero omite los bloques `Ref` separados al final del archivo**: el regex para refs inline (`\[ref: > tabla.campo\]`) funciona, pero el regex para bloques separados `Ref: tabla1.campo > tabla2.campo` puede no capturar todas las variantes de formato (con nombre de ref, con acción on delete). Causa: el formato de bloques `Ref` tiene más variantes sintácticas que los refs inline. Fix: después del análisis heurístico, buscar explícitamente líneas que empiecen con `Ref` y reportarlas por separado al usuario para revisión manual.

## Referencias

- DBML oficial: https://dbml.dbdiagram.io/docs/
- `@dbml/core` en npm: https://www.npmjs.com/package/@dbml/core
- Instalación y atribución: `MANUAL_USO.md` → Dependencias externas → `@dbml/core`.
