---
name: perfex-database
description: Operações de base de dados em módulos Perfex CRM — db_prefix(), options, queries e Active Record. Baseado apenas na documentação oficial.
---

# /perfex-database - Base de Dados Perfex CRM

Operações de base de dados em módulos. **Zero assumptions, zero hallucinations** - apenas documentação oficial.

---

## Documentação Base

- [Module Basics](https://help.perfexcrm.com/module-basics/)
- [CodeIgniter Query Builder](https://codeigniter.com/userguide3/database/query_builder.html)

---

## Regra Fundamental: db_prefix()

**SEMPRE usar `db_prefix()` em todas as queries.**

O Perfex CRM suporta prefixos de tabela customizados. O default é `tbl`, mas pode ser alterado.

```php
// CORRECTO
$CI->db->get(db_prefix() . 'meu_modulo');

// ERRADO - quebra se prefix não for "tbl"
$CI->db->get('tblmeu_modulo');
```

---

## Aceder à Base de Dados

### Em Controllers/Models

```php
// Acesso directo
$this->db->get(db_prefix() . 'clients');
```

### Fora de Controllers/Models

```php
$CI = &get_instance();
$CI->db->get(db_prefix() . 'clients');
```

---

## Criar Tabelas (Activation Hook)

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

    // Verificar se tabela existe
    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,
                `description` TEXT,
                `amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00,
                `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 `status` (`status`)
            ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';
        ');
    }
}
```

---

## Query Builder (Active Record)

### SELECT

```php
// Todos os registos
$result = $this->db->get(db_prefix() . 'meu_modulo')->result();

// Com condições
$this->db->where('status', 'active');
$this->db->where('client_id', $client_id);
$result = $this->db->get(db_prefix() . 'meu_modulo')->result();

// Seleccionar campos específicos
$this->db->select('id, name, amount');
$this->db->where('status', 'active');
$result = $this->db->get(db_prefix() . 'meu_modulo')->result();

// Um registo
$this->db->where('id', $id);
$row = $this->db->get(db_prefix() . 'meu_modulo')->row();

// Com LIKE
$this->db->like('name', $search);
$result = $this->db->get(db_prefix() . 'meu_modulo')->result();

// Com ORDER BY
$this->db->order_by('created_at', 'DESC');
$result = $this->db->get(db_prefix() . 'meu_modulo')->result();

// Com LIMIT
$this->db->limit(10, 0); // 10 registos, offset 0
$result = $this->db->get(db_prefix() . 'meu_modulo')->result();

// JOIN
$this->db->select('m.*, c.company as client_name');
$this->db->from(db_prefix() . 'meu_modulo as m');
$this->db->join(db_prefix() . 'clients as c', 'c.userid = m.client_id', 'left');
$result = $this->db->get()->result();
```

### INSERT

```php
$data = [
    'client_id'   => $client_id,
    'name'        => $this->input->post('name'),
    'description' => $this->input->post('description'),
    'amount'      => $this->input->post('amount'),
    'status'      => 'pending',
    'created_at'  => date('Y-m-d H:i:s'),
];

$this->db->insert(db_prefix() . 'meu_modulo', $data);
$insert_id = $this->db->insert_id();
```

### UPDATE

```php
$data = [
    'name'        => $this->input->post('name'),
    'description' => $this->input->post('description'),
    'amount'      => $this->input->post('amount'),
    'updated_at'  => date('Y-m-d H:i:s'),
];

$this->db->where('id', $id);
$this->db->update(db_prefix() . 'meu_modulo', $data);

// Verificar se actualizou
$affected = $this->db->affected_rows();
```

### DELETE

```php
$this->db->where('id', $id);
$this->db->delete(db_prefix() . 'meu_modulo');
```

### COUNT

```php
// Contar todos
$count = $this->db->count_all(db_prefix() . 'meu_modulo');

// Contar com condições
$this->db->where('status', 'active');
$count = $this->db->count_all_results(db_prefix() . 'meu_modulo');
```

---

## Sistema de Opções

Para guardar configurações simples (key-value).

### add_option()

Cria opção nova. **NÃO sobrescreve se existir.**

```php
add_option('meu_modulo_enabled', '1');
add_option('meu_modulo_config', serialize(['key' => 'value']));

// Com autoload (0 = não, 1 = sim)
add_option('meu_modulo_setting', 'valor', 0);
```

### get_option()

Obtém valor da opção.

```php
$enabled = get_option('meu_modulo_enabled');
$config = unserialize(get_option('meu_modulo_config'));

// Retorna string vazia se não existir
if (get_option('meu_modulo_setting') === '') {
    // Opção não existe
}
```

### update_option()

Actualiza ou cria opção (v2.3.3+).

```php
update_option('meu_modulo_enabled', '0');
update_option('meu_modulo_last_sync', date('Y-m-d H:i:s'));
```

### delete_option()

Remove opção.

```php
delete_option('meu_modulo_enabled');
```

### Boas Práticas Opções

```php
// SEMPRE prefixar com nome do módulo
add_option('meu_modulo_version', '1.0.0');
add_option('meu_modulo_api_key', '');
add_option('meu_modulo_enabled', '1');

// NUNCA usar nomes genéricos
add_option('enabled', '1');        // ERRADO
add_option('api_key', '...');      // ERRADO
```

---

## Model Exemplo Completo

```php
<?php

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

class Meu_modulo_model extends App_Model
{
    private $table;

    public function __construct()
    {
        parent::__construct();
        $this->table = db_prefix() . 'meu_modulo';
    }

    /**
     * Obter todos os registos
     */
    public function get_all($where = [])
    {
        if (!empty($where)) {
            $this->db->where($where);
        }
        $this->db->order_by('created_at', 'DESC');
        return $this->db->get($this->table)->result();
    }

    /**
     * Obter por ID
     */
    public function get($id)
    {
        $this->db->where('id', $id);
        return $this->db->get($this->table)->row();
    }

    /**
     * Obter por cliente
     */
    public function get_by_client($client_id)
    {
        $this->db->where('client_id', $client_id);
        $this->db->order_by('created_at', 'DESC');
        return $this->db->get($this->table)->result();
    }

    /**
     * Criar novo registo
     */
    public function add($data)
    {
        $data['created_at'] = date('Y-m-d H:i:s');

        // Trigger hook antes
        $data = hooks()->apply_filters('before_meu_modulo_added', $data);

        $this->db->insert($this->table, $data);
        $insert_id = $this->db->insert_id();

        if ($insert_id) {
            // Trigger hook após
            hooks()->do_action('after_meu_modulo_added', $insert_id);
            log_activity('Meu Módulo criado [ID: ' . $insert_id . ']');
        }

        return $insert_id;
    }

    /**
     * Actualizar registo
     */
    public function update($id, $data)
    {
        $data['updated_at'] = date('Y-m-d H:i:s');

        // Trigger hook antes
        $data = hooks()->apply_filters('before_meu_modulo_updated', $data, $id);

        $this->db->where('id', $id);
        $this->db->update($this->table, $data);

        if ($this->db->affected_rows() > 0) {
            // Trigger hook após
            hooks()->do_action('after_meu_modulo_updated', $id);
            log_activity('Meu Módulo actualizado [ID: ' . $id . ']');
            return true;
        }

        return false;
    }

    /**
     * Apagar registo
     */
    public function delete($id)
    {
        // Trigger hook antes
        hooks()->do_action('before_meu_modulo_deleted', $id);

        $this->db->where('id', $id);
        $this->db->delete($this->table);

        if ($this->db->affected_rows() > 0) {
            // Trigger hook após
            hooks()->do_action('after_meu_modulo_deleted', $id);
            log_activity('Meu Módulo apagado [ID: ' . $id . ']');
            return true;
        }

        return false;
    }

    /**
     * Contar por status
     */
    public function count_by_status($status)
    {
        $this->db->where('status', $status);
        return $this->db->count_all_results($this->table);
    }

    /**
     * Pesquisa
     */
    public function search($term)
    {
        $this->db->group_start();
        $this->db->like('name', $term);
        $this->db->or_like('description', $term);
        $this->db->group_end();

        return $this->db->get($this->table)->result();
    }
}
```

---

## Transações

Para operações que devem ser atómicas:

```php
$this->db->trans_begin();

try {
    $this->db->insert(db_prefix() . 'meu_modulo', $data1);
    $id = $this->db->insert_id();

    $this->db->insert(db_prefix() . 'meu_modulo_items', [
        'parent_id' => $id,
        'item'      => $item,
    ]);

    if ($this->db->trans_status() === false) {
        $this->db->trans_rollback();
        return false;
    }

    $this->db->trans_commit();
    return $id;

} catch (Exception $e) {
    $this->db->trans_rollback();
    log_activity('Erro transação: ' . $e->getMessage());
    return false;
}
```

---

## Funções Úteis

### log_activity()

Registar actividade no sistema.

```php
log_activity('Descrição da actividade');
log_activity('Item criado [ID: ' . $id . ', Cliente: ' . $client_id . ']');
```

### Datas

```php
// Data actual formatada
$now = date('Y-m-d H:i:s');

// Formatar para display
$formatted = _d($row->created_at);      // Data
$formatted = _dt($row->created_at);     // Data e hora
```

---

## Anti-Patterns (NUNCA FAZER)

| Anti-Pattern | Risco | Alternativa |
|--------------|-------|-------------|
| Query sem db_prefix() | Falha em instalações custom | `db_prefix()` sempre |
| SQL directo com input user | SQL Injection | Query Builder |
| Opções sem prefixo módulo | Conflitos | Prefixar sempre |
| Não usar transações em multi-insert | Dados inconsistentes | `trans_begin/commit` |
| Hardcode charset | Problemas encoding | `$CI->db->char_set` |

---

## Checklist Database

```
1. [ ] db_prefix() em TODAS as queries
2. [ ] $CI->db->char_set na criação de tabelas
3. [ ] Opções com prefixo do módulo
4. [ ] Query Builder para inputs de utilizador
5. [ ] Transações para operações múltiplas
6. [ ] log_activity() para auditoria
7. [ ] Hooks em operações CRUD
8. [ ] Índices em colunas de pesquisa
```

---

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

---

## 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.*
