# 04 - Migraciones y Seeders

## Migraciones

### Comandos

```bash
php bin/migrate status   # Ver estado de migraciones
php bin/migrate up       # Ejecutar migraciones pendientes
php bin/migrate down     # Revertir la última migración
```

### Crear migración

Se genera automáticamente al crear un módulo:

```bash
php bin/make module Producto
# Crea: database/migrations/{timestamp}_create_producto_table.php
```

### Estructura de una migración

```php
<?php

class CreateProductoTable
{
    public function up(PDO $pdo)
    {
        $pdo->exec("
            CREATE TABLE IF NOT EXISTS productos (
                Id INT AUTO_INCREMENT PRIMARY KEY,
                Nombre VARCHAR(100) NOT NULL,
                Codigo VARCHAR(100) NOT NULL,
                Descripcion TEXT NULL,
                Precio DECIMAL(12,2) NOT NULL DEFAULT 0,
                Stock INT NOT NULL DEFAULT 0,
                CategoriaId INT NULL,
                Estado INT NOT NULL DEFAULT 1,
                FechaRegistro DATETIME NULL,
                UsuarioRegistro INT NULL,
                FechaModificacion DATETIME NULL,
                UsuarioModificacion INT NULL,
                TenantId INT NOT NULL DEFAULT 1
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        ");
    }

    public function down(PDO $pdo)
    {
        $pdo->exec("DROP TABLE IF EXISTS productos;");
    }
}
```

### Reglas de migraciones

1. **Siempre implementar `up()` y `down()`** — toda migración debe ser reversible.
2. **No editar migraciones ya aplicadas** en ambientes compartidos.
3. **Cambios incrementales** — para modificar una tabla existente, crear una nueva migración:

```php
<?php

class AddTelefonoToProductos
{
    public function up(PDO $pdo)
    {
        $pdo->exec("ALTER TABLE productos ADD COLUMN Telefono VARCHAR(50) NULL AFTER Descripcion;");
    }

    public function down(PDO $pdo)
    {
        $pdo->exec("ALTER TABLE productos DROP COLUMN Telefono;");
    }
}
```

4. **Naming:** El nombre del archivo debe ser descriptivo:
   - `{timestamp}_create_{tabla}_table.php` — creación de tabla.
   - `{timestamp}_add_{columna}_to_{tabla}.php` — agregar columna.
   - `{timestamp}_modify_{tabla}_{detalle}.php` — modificación general.

5. **TenantId:** Si la tabla debe soportar multi-tenancy, incluir columna `TenantId INT NOT NULL DEFAULT 1`.

6. **Columnas de auditoría comunes:**
   - `FechaRegistro DATETIME` — fecha de creación.
   - `UsuarioRegistro INT` — usuario creador.
   - `FechaModificacion DATETIME` — última modificación.
   - `UsuarioModificacion INT` — usuario que modificó.

### Tabla de control

Las migraciones se rastrean en la tabla `migrations` de la BD:

```sql
CREATE TABLE migrations (
    id INT AUTO_INCREMENT PRIMARY KEY,
    migration VARCHAR(255) NOT NULL,
    batch INT NOT NULL,
    executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

## Seeders

### Comandos

```bash
php bin/seed                    # Ejecutar grupo demo (default)
php bin/seed --group=demo       # Ejecutar dataset demo completo
php bin/seed --group=klee       # Ejecutar baseline mínimo para implementación (prod inicial)
php bin/seed ProductoSeeder     # Ejecutar un seeder específico
php bin/seed --list             # Listar seeders del grupo actual (demo por defecto)
php bin/seed --list --group=klee
```

### Grupos de semillas

- `demo`: Roles/usuarios demo, permisos/configuración base y datos operativos de monitoreo (estaciones, sensores, lecturas, alertas, mantenimientos).
- `klee`: Baseline mínimo para implementación: rol `admin` + usuario `admin` + permisos/configuración base.

Recomendación:

- Entorno local/demo: usar `--group=demo`.
- Implementación inicial/producción: usar `--group=klee`.

### Crear seeder

```bash
php bin/make seeder EstadosCatalogoSeeder
# Crea: database/seeders/EstadosCatalogoSeeder.php
```

### Estructura de un seeder

```php
<?php

class ProductoSeeder
{
    protected $order = 100;  // Orden de ejecución

    public function run(PDO $pdo)
    {
        $registros = array(
            array('Nombre' => 'Producto Demo 1', 'Codigo' => 'PROD-001', 'Precio' => 29.99, 'Stock' => 100, 'Estado' => 1, 'TenantId' => 1),
            array('Nombre' => 'Producto Demo 2', 'Codigo' => 'PROD-002', 'Precio' => 49.99, 'Stock' => 50, 'Estado' => 1, 'TenantId' => 1),
        );

        $stmt = $pdo->prepare("
            INSERT INTO productos (Nombre, Codigo, Precio, Stock, Estado, TenantId)
            VALUES (:Nombre, :Codigo, :Precio, :Stock, :Estado, :TenantId)
        ");

        foreach ($registros as $registro) {
            $stmt->execute($registro);
        }

        echo "  [ok] ProductoSeeder: " . count($registros) . " registros insertados.\n";
    }
}
```

### Seeder de permisos

El generador crea automáticamente un seeder de permisos que registra las acciones CRUD para un rol administrador:

```php
<?php

class ProductoPermissionsSeeder
{
    protected $order = 60;

    public function run(PDO $pdo)
    {
        $moduleName = 'Producto';
        $actions = array('list', 'view', 'create', 'edit', 'remove', 'dataListAjax');

        // Buscar rol administrador
        $stmt = $pdo->query("SELECT Id FROM roles WHERE Nombre = 'Administrador' LIMIT 1");
        $roleId = $stmt->fetchColumn();

        if (!$roleId) {
            echo "  [skip] No se encontró rol Administrador.\n";
            return;
        }

        $insert = $pdo->prepare("
            INSERT IGNORE INTO permisos (RolId, Modulo, Accion, Permiso, TenantId)
            VALUES (:RolId, :Modulo, :Accion, 1, 1)
        ");

        foreach ($actions as $action) {
            $insert->execute(array(
                'RolId'  => $roleId,
                'Modulo' => $moduleName,
                'Accion' => $action,
            ));
        }

        echo "  [ok] {$moduleName}PermissionsSeeder: " . count($actions) . " permisos asignados.\n";
    }
}
```

### Convención de orden (`$order`)

| Rango | Uso |
|---|---|
| 10-40 | Core del sistema (roles, tipos, estados base) |
| 50-69 | Permisos de módulos |
| 70-99 | Datos de aplicación |
| 100+ | Datos de prueba/desarrollo |

### Idempotencia

Los seeders deben ser idealmente idempotentes (ejecutables múltiples veces sin duplicar datos). Usar `INSERT IGNORE` o verificar existencia antes de insertar:

```php
$existing = $pdo->query("SELECT COUNT(*) FROM categorias")->fetchColumn();
if ($existing > 0) {
    echo "  [skip] CategoriaSeeder: ya existen datos.\n";
    return;
}
```

## Secuencia de despliegue

```bash
# 1. Ejecutar migraciones pendientes
php bin/migrate up

# 2a. Seed para demo/local
php bin/seed --group=demo

# 2b. Seed para implementación/producción inicial
php bin/seed --group=klee

# 3. Verificar estado del sistema
php bin/doctor

# 4. (Opcional) Verificar conectividad HTTP
php bin/smoke
```
