---
name: perfex-migrations
description: Migrações e upgrades de módulos Perfex CRM — gestão de versões, ficheiros de migração e actualizações de base de dados. Baseado apenas na documentação oficial.
---

# /perfex-migrations - Migrations Perfex CRM

Sistema de migrações e upgrades de módulos. **Zero assumptions, zero hallucinations** - apenas documentação oficial.

---

## Documentação Base

- [Preparing Module Upgrade](https://help.perfexcrm.com/preparing-module-upgrade/)
- [Module Basics](https://help.perfexcrm.com/module-basics/)

---

## Como Funciona

O Perfex CRM compara:
- **Versão no init file** (header `Version:`)
- **Versão na base de dados** (tabela `tblmodules`)

Se forem diferentes → Sistema mostra "Database upgrade required".

---

## Estrutura de Migrations

```
modules/meu_modulo/
├── meu_modulo.php          # Init file com Version: X.Y.Z
└── migrations/
    ├── 001_version_100.php  # v1.0.0 → v1.0.1
    ├── 002_version_101.php  # v1.0.1 → v1.0.2
    ├── 100_version_100.php  # v1.0.0 inicial (alternativa)
    ├── 110_version_110.php  # v1.1.0
    └── 200_version_200.php  # v2.0.0
```

---

## Formato do Ficheiro

### Nomenclatura (CRÍTICO)

O Perfex usa **Sequential Migration** (não Timestamp).

```
NNN_version_NNN.php
│   │       │
│   │       └── Versão sem pontos (110 = 1.1.0)
│   └── Literal "version"
└── Número sequencial (3 dígitos, sem gaps)
```

**Exemplos:**

| Versão | Ficheiro |
|--------|----------|
| 1.0.0 | `001_version_100.php` ou `100_version_100.php` |
| 1.0.1 | `002_version_101.php` ou `101_version_101.php` |
| 1.1.0 | `003_version_110.php` ou `110_version_110.php` |
| 2.0.0 | `004_version_200.php` ou `200_version_200.php` |

**Convenção recomendada:** Usar versão como prefixo (100, 110, 200...).

---

## Template Migration

```php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_110 extends App_module_migration
{
    /**
     * Executado durante upgrade
     */
    public function up()
    {
        // Alterações de base de dados aqui
    }
}
```

### Notas Importantes

1. **Classe:** `Migration_Version_NNN` (NNN = versão sem pontos)
2. **Extends:** `App_module_migration`
3. **Método:** Apenas `up()` - não há `down()` (downgrades não suportados)
4. **Pode ser vazio:** Se não houver alterações de BD, deixar `up()` vazio

---

## Exemplos de Migrations

### Adicionar Coluna

```php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_110 extends App_module_migration
{
    public function up()
    {
        $CI = &get_instance();

        // Adicionar coluna se não existir
        if (!$CI->db->field_exists('new_column', db_prefix() . 'meu_modulo')) {
            $CI->db->query('ALTER TABLE `' . db_prefix() . 'meu_modulo`
                ADD COLUMN `new_column` VARCHAR(255) DEFAULT NULL AFTER `name`');
        }
    }
}
```

### Criar Nova Tabela

```php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_120 extends App_module_migration
{
    public function up()
    {
        $CI = &get_instance();

        if (!$CI->db->table_exists(db_prefix() . 'meu_modulo_items')) {
            $CI->db->query('
                CREATE TABLE `' . db_prefix() . 'meu_modulo_items` (
                    `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
                    `parent_id` INT(11) UNSIGNED NOT NULL,
                    `name` VARCHAR(255) NOT NULL,
                    `quantity` INT(11) NOT NULL DEFAULT 1,
                    PRIMARY KEY (`id`),
                    KEY `parent_id` (`parent_id`)
                ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';
            ');
        }
    }
}
```

### Modificar Coluna

```php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_130 extends App_module_migration
{
    public function up()
    {
        $CI = &get_instance();

        // Alterar tipo de coluna
        $CI->db->query('ALTER TABLE `' . db_prefix() . 'meu_modulo`
            MODIFY COLUMN `amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00');
    }
}
```

### Adicionar Índice

```php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_140 extends App_module_migration
{
    public function up()
    {
        $CI = &get_instance();

        // Verificar se índice existe (evitar erro)
        $indexes = $CI->db->query('SHOW INDEX FROM `' . db_prefix() . 'meu_modulo`
            WHERE Key_name = "idx_status"')->result();

        if (count($indexes) == 0) {
            $CI->db->query('ALTER TABLE `' . db_prefix() . 'meu_modulo`
                ADD INDEX `idx_status` (`status`)');
        }
    }
}
```

### Migração de Dados

```php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_150 extends App_module_migration
{
    public function up()
    {
        $CI = &get_instance();

        // Migrar dados existentes
        $CI->db->query('UPDATE `' . db_prefix() . 'meu_modulo`
            SET `status` = "active"
            WHERE `status` = "1"');

        $CI->db->query('UPDATE `' . db_prefix() . 'meu_modulo`
            SET `status` = "inactive"
            WHERE `status` = "0"');
    }
}
```

### Adicionar Opção

```php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_160 extends App_module_migration
{
    public function up()
    {
        // Adicionar nova opção (add_option não sobrescreve)
        add_option('meu_modulo_new_feature', '1');
        add_option('meu_modulo_default_status', 'pending');
    }
}
```

### Migration Vazia (Só actualiza versão)

```php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_170 extends App_module_migration
{
    public function up()
    {
        // Sem alterações de BD nesta versão
        // O Perfex apenas actualiza o número da versão
    }
}
```

---

## Sincronizar Activation Hook

**CRÍTICO:** O activation hook deve SEMPRE conter o schema mais recente.

```php
// modules/meu_modulo/meu_modulo.php

function meu_modulo_activation_hook()
{
    $CI = &get_instance();

    // Schema completo e actualizado (v1.7.0)
    if (!$CI->db->table_exists(db_prefix() . 'meu_modulo')) {
        $CI->db->query('
            CREATE TABLE `' . db_prefix() . 'meu_modulo` (
                `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
                `client_id` INT(11) UNSIGNED NOT NULL,
                `name` VARCHAR(255) NOT NULL,
                `new_column` VARCHAR(255) DEFAULT NULL,  -- Adicionado v1.1.0
                `description` TEXT,
                `amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00,  -- Modificado v1.3.0
                `status` VARCHAR(50) NOT NULL DEFAULT "pending",
                `created_at` DATETIME NOT NULL,
                `updated_at` DATETIME DEFAULT NULL,
                PRIMARY KEY (`id`),
                KEY `client_id` (`client_id`),
                KEY `idx_status` (`status`)  -- Adicionado v1.4.0
            ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';
        ');
    }

    // Tabela items (adicionada v1.2.0)
    if (!$CI->db->table_exists(db_prefix() . 'meu_modulo_items')) {
        $CI->db->query('
            CREATE TABLE `' . db_prefix() . 'meu_modulo_items` (
                `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
                `parent_id` INT(11) UNSIGNED NOT NULL,
                `name` VARCHAR(255) NOT NULL,
                `quantity` INT(11) NOT NULL DEFAULT 1,
                PRIMARY KEY (`id`),
                KEY `parent_id` (`parent_id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';
        ');
    }

    // Opções (versão mais recente)
    add_option('meu_modulo_version', '1.7.0');
    add_option('meu_modulo_new_feature', '1');      // v1.6.0
    add_option('meu_modulo_default_status', 'pending');  // v1.6.0
}
```

---

## Workflow de Release

### 1. Desenvolver Alterações

```php
// Exemplo: Adicionar campo "priority" na v1.8.0
```

### 2. Criar Migration

```php
// migrations/180_version_180.php

<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Migration_Version_180 extends App_module_migration
{
    public function up()
    {
        $CI = &get_instance();

        if (!$CI->db->field_exists('priority', db_prefix() . 'meu_modulo')) {
            $CI->db->query('ALTER TABLE `' . db_prefix() . 'meu_modulo`
                ADD COLUMN `priority` TINYINT(1) NOT NULL DEFAULT 0 AFTER `status`');
        }
    }
}
```

### 3. Actualizar Activation Hook

```php
// Adicionar campo ao CREATE TABLE no activation hook
`priority` TINYINT(1) NOT NULL DEFAULT 0,
```

### 4. Actualizar Versão no Header

```php
/*
Module Name: Meu Módulo
Version: 1.8.0    <!-- ACTUALIZAR -->
Requires at least: 2.3.*
*/
```

### 5. Testar

1. Instalação limpa (activation hook)
2. Upgrade de versão anterior (migration)

---

## Verificações de Segurança em Migrations

```php
public function up()
{
    $CI = &get_instance();

    // Verificar se tabela existe antes de ALTER
    if (!$CI->db->table_exists(db_prefix() . 'meu_modulo')) {
        return;  // Tabela não existe, nada a fazer
    }

    // Verificar se coluna existe antes de ADD
    if ($CI->db->field_exists('new_column', db_prefix() . 'meu_modulo')) {
        return;  // Coluna já existe
    }

    // Seguro para executar
    $CI->db->query('ALTER TABLE ...');
}
```

---

## Anti-Patterns (NUNCA FAZER)

| Anti-Pattern | Risco | Alternativa |
|--------------|-------|-------------|
| Gaps na sequência (001, 003) | Migrations saltadas | Sequência contínua |
| Não actualizar activation hook | Instalações novas incompletas | Sincronizar sempre |
| ALTER sem verificar existência | Erro se já existe | `field_exists()` |
| DROP sem backup | Dados perdidos | Backup ou soft delete |
| Hardcode prefix "tbl" | Falha em instalações custom | `db_prefix()` |

---

## Checklist Migrations

```
1. [ ] Ficheiro com nome correcto (NNN_version_NNN.php)
2. [ ] Classe com nome correcto (Migration_Version_NNN)
3. [ ] Extends App_module_migration
4. [ ] Verificações de existência antes de ALTER
5. [ ] db_prefix() em todas as queries
6. [ ] Activation hook actualizado com schema completo
7. [ ] Header Version actualizado
8. [ ] Testado: instalação limpa
9. [ ] Testado: upgrade de versão anterior
10. [ ] Sem gaps na sequência de migrations
```

---

**Versão:** 1.0.0 | **Autor:** Descomplicar®
**Fonte:** help.perfexcrm.com/preparing-module-upgrade

---

## Healing Log

Registo de erros conhecidos e como evitá-los. Lido automaticamente antes de executar.

```jsonl
{"date":"","issue":"","fix":"","source":"user|auto"}
```

*Adicionar nova linha após cada erro corrigido.*
