PC SOFT

PROFESSIONAL NEWSGROUPS
WINDEVWEBDEV and WINDEV Mobile

Home → WINDEV 25 → Exemplo SUPABASE
Exemplo SUPABASE
Started by Boller, Aug., 23 2025 12:26 AM - 1 reply
Registered member
4,618 messages
Posted on August, 23 2025 - 12:26 AM
Vou criar um exemplo completo de integração entre Supabase (PostgreSQL) e Windev para um sistema de pedidos com tabelas relacionadas.​​​​​​​​​​​​​​​​

Criei um exemplo completo de integração entre Supabase (PostgreSQL) e Windev para um sistema de pedidos. O exemplo inclui:

## **Estrutura do Banco (Supabase/PostgreSQL):**

- Tabelas `pedidos` e `pedidos_itens` com relacionamento
- Triggers automáticos para calcular valor total
- Índices para otimização de performance
- Row Level Security (RLS) opcional

## **Implementação Windev:**

- Classes `CPedido` e `CPedidoItem` para encapsular a lógica
- Métodos para operações CRUD via API REST do Supabase
- Tratamento adequado de JSON e requisições HTTP
- Exemplo de uso em interface

## **Funcionalidades Implementadas:**

- ✅ Inserção de pedidos e itens
- ✅ Atualização automática de valores totais
- ✅ Busca de pedidos e itens
- ✅ Relacionamento entre tabelas
- ✅ Validações e tratamento de erros

## **Próximos Passos:**

1. Configure seu projeto no Supabase e execute os SQLs
1. Substitua as constantes pelas suas chaves reais
1. Adapte as classes conforme suas necessidades específicas
1. Implemente validações adicionais conforme seu negócio

# Sistema de Pedidos - Supabase + Windev

## 1. Estrutura do Banco de Dados no Supabase (PostgreSQL)

### Tabela: pedidos

```sql
-- Criar tabela pedidos
CREATE TABLE pedidos (
id SERIAL PRIMARY KEY,
numero_pedido VARCHAR(20) UNIQUE NOT NULL,
cliente_id INTEGER,
data_pedido TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
valor_total DECIMAL(10,2) DEFAULT 0.00,
status VARCHAR(20) DEFAULT 'PENDENTE',
observacoes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Índices para otimização
CREATE INDEX idx_pedidos_cliente ON pedidos(cliente_id);
CREATE INDEX idx_pedidos_data ON pedidos(data_pedido);
CREATE INDEX idx_pedidos_status ON pedidos(status);
```

### Tabela: pedidos_itens

```sql
-- Criar tabela pedidos_itens
CREATE TABLE pedidos_itens (
id SERIAL PRIMARY KEY,
pedido_id INTEGER NOT NULL,
produto_id INTEGER NOT NULL,
descricao_produto VARCHAR(255),
quantidade DECIMAL(10,3) DEFAULT 1.000,
valor_unitario DECIMAL(10,2) DEFAULT 0.00,
valor_total DECIMAL(10,2) DEFAULT 0.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

-- Chave estrangeira
CONSTRAINT fk_pedidos_itens_pedido
FOREIGN KEY (pedido_id) REFERENCES pedidos(id)
ON DELETE CASCADE
);

-- Índices
CREATE INDEX idx_pedidos_itens_pedido ON pedidos_itens(pedido_id);
CREATE INDEX idx_pedidos_itens_produto ON pedidos_itens(produto_id);
```

### Função para atualizar valor total do pedido

```sql
-- Função para calcular valor total do pedido
CREATE OR REPLACE FUNCTION atualizar_valor_total_pedido()
RETURNS TRIGGER AS $$
BEGIN
UPDATE pedidos
SET valor_total = (
SELECT COALESCE(SUM(valor_total), 0.00)
FROM pedidos_itens
WHERE pedido_id = COALESCE(NEW.pedido_id, OLD.pedido_id)
),
updated_at = CURRENT_TIMESTAMP
WHERE id = COALESCE(NEW.pedido_id, OLD.pedido_id);

RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

-- Trigger para atualizar automaticamente o valor total
CREATE TRIGGER trigger_atualizar_valor_pedido
AFTER INSERT OR UPDATE OR DELETE ON pedidos_itens
FOR EACH ROW
EXECUTE FUNCTION atualizar_valor_total_pedido();
```

### RLS (Row Level Security) - Opcional

```sql
-- Habilitar RLS nas tabelas
ALTER TABLE pedidos ENABLE ROW LEVEL SECURITY;
ALTER TABLE pedidos_itens ENABLE ROW LEVEL SECURITY;

-- Política básica (ajustar conforme necessidade)
CREATE POLICY "Permitir tudo para usuários autenticados" ON pedidos
FOR ALL USING (auth.role() = 'authenticated');

CREATE POLICY "Permitir tudo para usuários autenticados" ON pedidos_itens
FOR ALL USING (auth.role() = 'authenticated');
```

## 2. Configuração no Windev

### Estrutura de Dados no Análise Windev

#### Arquivo: Pedidos

```
Pedidos
├── ID (Numérico, Chave primária, Auto-incremento)
├── NumeroPedido (Texto, 20 caracteres, Único)
├── ClienteID (Numérico)
├── DataPedido (Data e Hora)
├── ValorTotal (Monetário)
├── Status (Texto, 20 caracteres)
├── Observacoes (Texto longo)
├── CreatedAt (Data e Hora)
└── UpdatedAt (Data e Hora)
```

#### Arquivo: PedidosItens

```
PedidosItens
├── ID (Numérico, Chave primária, Auto-incremento)
├── PedidoID (Numérico, Chave estrangeira para Pedidos)
├── ProdutoID (Numérico)
├── DescricaoProduto (Texto, 255 caracteres)
├── Quantidade (Numérico real)
├── ValorUnitario (Monetário)
├── ValorTotal (Monetário)
└── CreatedAt (Data e Hora)
```

### Conexão com Supabase

#### Código de Conexão (Inicialização do projeto)

```wlanguage
// Declarações globais ou classe de conexão
CONSTANTE
SUPABASE_URL = "https://seuprojetoid.supabase.co"
SUPABASE_ANON_KEY = "sua-chave-anonima-aqui"
SUPABASE_SERVICE_KEY = "sua-chave-de-servico-aqui" // Para operações admin
FIM

// Variáveis globais
gsSupabaseURL é string = SUPABASE_URL
gsSupabaseKey é string = SUPABASE_ANON_KEY

// Função para configurar headers das requisições
PROCEDIMENTO ConfigurarHeadersSupabase()
HTTPReinicializar()
HTTPAdicionarHeader("Content-Type", "application/json")
HTTPAdicionarHeader("apikey", gsSupabaseKey)
HTTPAdicionarHeader("Authorization", "Bearer " + gsSupabaseKey)
FIM
```

### Classes de Manipulação

#### Classe: CPedido

```wlanguage
CPedido é uma Classe
// Propriedades
m_nID é inteiro
m_sNumeroPedido é string
m_nClienteID é inteiro
m_dhDataPedido é DataHora
m_rValorTotal é real
m_sStatus é string = "PENDENTE"
m_sObservacoes é string
m_dhCreatedAt é DataHora
m_dhUpdatedAt é DataHora

// Lista de itens
m_arrItens é array de CPedidoItem
FIM

// Método: Inserir pedido
PROCEDIMENTO CPedido::Inserir()
LOCAL
sJSON é string
sURL é string
sResposta é string
vResposta é Variant
FIM

// Preparar dados para inserção
sJSON = [
{
"numero_pedido": "%1",
"cliente_id": %2,
"data_pedido": "%3",
"status": "%4",
"observacoes": "%5"
}
]

sJSON = StringFormatar(sJSON, ...:m_sNumeroPedido, ...:m_nClienteID, ..
DateTimeParaString(m_dhDataPedido, "YYYY-MM-DD HH:mm:ss"), ..
:m_sStatus, :m_sObservacoes)

sURL = gsSupabaseURL + "/rest/v1/pedidos"

ConfigurarHeadersSupabase()
HTTPAdicionarHeader("Prefer", "return=representation")

sResposta = HTTPRequisição(httpPost, sURL, sJSON)

SE HTTPObterResultado() = 200 OU HTTPObterResultado() = 201 ENTÃO
vResposta = JSONParaVariant(sResposta)
:m_nID = vResposta[1].id
:m_dhCreatedAt = StringParaDataHora(vResposta[1].created_at)
:m_dhUpdatedAt = StringParaDataHora(vResposta[1].updated_at)
RESULTADO Verdadeiro
SENÃO
Erro("Erro ao inserir pedido: " + sResposta)
RESULTADO Falso
FIM
FIM

// Método: Atualizar pedido
PROCEDIMENTO CPedido::Atualizar()
LOCAL
sJSON é string
sURL é string
sResposta é string
FIM

sJSON = [
{
"cliente_id": %1,
"status": "%2",
"observacoes": "%3",
"updated_at": "%4"
}
]

sJSON = StringFormatar(sJSON, :m_nClienteID, :m_sStatus, ..
:m_sObservacoes, DateTimeParaString(DateTimeSys(), "YYYY-MM-DD HH:mm:ss"))

sURL = gsSupabaseURL + "/rest/v1/pedidos?id=eq." + :m_nID

ConfigurarHeadersSupabase()

sResposta = HTTPRequisição(httpPatch, sURL, sJSON)

SE HTTPObterResultado() = 200 OU HTTPObterResultado() = 204 ENTÃO
RESULTADO Verdadeiro
SENÃO
Erro("Erro ao atualizar pedido: " + sResposta)
RESULTADO Falso
FIM
FIM

// Método: Buscar pedido por ID
PROCEDIMENTO CPedido::BuscarPorID(nID é inteiro)
LOCAL
sURL é string
sResposta é string
vResposta é Variant
FIM

sURL = gsSupabaseURL + "/rest/v1/pedidos?id=eq." + nID + "&select=*"

ConfigurarHeadersSupabase()

sResposta = HTTPRequisição(httpGet, sURL)

SE HTTPObterResultado() = 200 ENTÃO
vResposta = JSONParaVariant(sResposta)
SE TableauTaille(vResposta) > 0 ENTÃO
:m_nID = vResposta[1].id
:m_sNumeroPedido = vResposta[1].numero_pedido
:m_nClienteID = vResposta[1].cliente_id
:m_dhDataPedido = StringParaDataHora(vResposta[1].data_pedido)
:m_rValorTotal = vResposta[1].valor_total
:m_sStatus = vResposta[1].status
:m_sObservacoes = vResposta[1].observacoes
:m_dhCreatedAt = StringParaDataHora(vResposta[1].created_at)
:m_dhUpdatedAt = StringParaDataHora(vResposta[1].updated_at)
RESULTADO Verdadeiro
FIM
FIM

RESULTADO Falso
FIM
```

#### Classe: CPedidoItem

```wlanguage
CPedidoItem é uma Classe
// Propriedades
m_nID é inteiro
m_nPedidoID é inteiro
m_nProdutoID é inteiro
m_sDescricaoProduto é string
m_rQuantidade é real = 1.0
m_rValorUnitario é real = 0.0
m_rValorTotal é real = 0.0
m_dhCreatedAt é DataHora
FIM

// Método: Calcular valor total
PROCEDIMENTO CPedidoItem::CalcularValorTotal()
:m_rValorTotal = :m_rQuantidade * :m_rValorUnitario
FIM

// Método: Inserir item
PROCEDIMENTO CPedidoItem::Inserir()
LOCAL
sJSON é string
sURL é string
sResposta é string
vResposta é Variant
FIM

// Calcular valor total antes de inserir
:CalcularValorTotal()

sJSON = [
{
"pedido_id": %1,
"produto_id": %2,
"descricao_produto": "%3",
"quantidade": %4,
"valor_unitario": %5,
"valor_total": %6
}
]

sJSON = StringFormatar(sJSON, :m_nPedidoID, :m_nProdutoID, ..
:m_sDescricaoProduto, :m_rQuantidade, ..
:m_rValorUnitario, :m_rValorTotal)

sURL = gsSupabaseURL + "/rest/v1/pedidos_itens"

ConfigurarHeadersSupabase()
HTTPAdicionarHeader("Prefer", "return=representation")

sResposta = HTTPRequisição(httpPost, sURL, sJSON)

SE HTTPObterResultado() = 200 OU HTTPObterResultado() = 201 ENTÃO
vResposta = JSONParaVariant(sResposta)
:m_nID = vResposta[1].id
:m_dhCreatedAt = StringParaDataHora(vResposta[1].created_at)
RESULTADO Verdadeiro
SENÃO
Erro("Erro ao inserir item do pedido: " + sResposta)
RESULTADO Falso
FIM
FIM

// Método: Buscar itens por pedido
PROCEDIMENTO BuscarItensPorPedido(nPedidoID é inteiro) : array de CPedidoItem
LOCAL
sURL é string
sResposta é string
vResposta é Variant
arrItens é array de CPedidoItem
oItem é CPedidoItem
nI é inteiro
FIM

sURL = gsSupabaseURL + "/rest/v1/pedidos_itens?pedido_id=eq." + nPedidoID + "&select=*&order=id"

ConfigurarHeadersSupabase()

sResposta = HTTPRequisição(httpGet, sURL)

SE HTTPObterResultado() = 200 ENTÃO
vResposta = JSONParaVariant(sResposta)

PARA nI = 1 _A_ TableauTaille(vResposta)
oItem = novo CPedidoItem()
oItem.m_nID = vResposta[nI].id
oItem.m_nPedidoID = vResposta[nI].pedido_id
oItem.m_nProdutoID = vResposta[nI].produto_id
oItem.m_sDescricaoProduto = vResposta[nI].descricao_produto
oItem.m_rQuantidade = vResposta[nI].quantidade
oItem.m_rValorUnitario = vResposta[nI].valor_unitario
oItem.m_rValorTotal = vResposta[nI].valor_total
oItem.m_dhCreatedAt = StringParaDataHora(vResposta[nI].created_at)

TableauAdicionar(arrItens, oItem)
FIM
FIM

RESULTADO arrItens
FIM
```

### Exemplo de Uso na Interface

#### Janela de Cadastro de Pedidos

```wlanguage
// Evento: Clique no botão Salvar
LOCAL
oPedido é CPedido
oItem é CPedidoItem
nI é inteiro
FIM

// Criar novo pedido
oPedido = novo CPedido()
oPedido.m_sNumeroPedido = SAI_NumeroPedido
oPedido.m_nClienteID = SAI_ClienteID
oPedido.m_dhDataPedido = SAI_DataPedido
oPedido.m_sStatus = COMBO_Status
oPedido.m_sObservacoes = SAI_Observacoes

// Inserir pedido
SE oPedido.Inserir() ENTÃO
// Inserir itens
PARA nI = 1 _A_ TableauTaille(TABELA_Itens)
oItem = novo CPedidoItem()
oItem.m_nPedidoID = oPedido.m_nID
oItem.m_nProdutoID = TABELA_Itens[nI].COL_ProdutoID
oItem.m_sDescricaoProduto = TABELA_Itens[nI].COL_Descricao
oItem.m_rQuantidade = TABELA_Itens[nI].COL_Quantidade
oItem.m_rValorUnitario = TABELA_Itens[nI].COL_ValorUnitario

SE NÃO oItem.Inserir() ENTÃO
Erro("Erro ao inserir item " + nI)
INTERRUPÇÃO
FIM
FIM

Info("Pedido salvo com sucesso!")
Fechar()
SENÃO
Erro("Erro ao salvar pedido")
FIM
```

### Configuração no Supabase Dashboard

1. **Criar o projeto no Supabase**
1. **Configurar as tabelas** usando o SQL Editor
1. **Configurar RLS** se necessário
1. **Obter as chaves de API**:
- Project URL
- Anon/Public Key
- Service Role Key (para operações administrativas)

### Dicas de Implementação

1. **Tratamento de Erros**: Sempre verificar os códigos de resposta HTTP
1. **Validação**: Implementar validações antes de enviar dados
1. **Cache**: Considerar cache local para melhorar performance
1. **Transações**: Para operações complexas, considere usar transações do PostgreSQL
1. **Logs**: Implementar logs para debugar problemas de integração

Este exemplo fornece uma base sólida para integração entre Supabase e Windev, permitindo operações CRUD completas em um sistema de pedidos.

Entendi que você quer focar no primeiro ponto. Vou detalhar especificamente a configuração do projeto no Supabase e a execução dos SQLs:​​​​​​​​​​​​​​​​

Criei um guia detalhado para a configuração do projeto no Supabase. O processo é dividido em etapas claras:

## **Principais Etapas:**

1. **Criar conta e projeto no Supabase** - Interface web simples
1. **Executar 4 scripts SQL sequenciais:**
- Criação das tabelas
- Índices para performance
- Função + Trigger automático
- Dados de teste (opcional)
1. **Verificar se tudo foi criado corretamente**
1. **Obter as chaves de API** necessárias para o Windev
1. **Testar a conexão** via REST API

## **Pontos Importantes:**

- ✅ Scripts organizados em sequência lógica
- ✅ Explicação de cada comando SQL
- ✅ Verificações para confirmar que funcionou
- ✅ Seção de troubleshooting para problemas comuns
- ✅ Teste da API REST antes de integrar com Windev

O trigger automático é especialmente útil - ele recalcula o valor total do pedido sempre que você adiciona, remove ou modifica itens, mantendo a consistência dos dados.


# Configuração do Projeto Supabase - Passo a Passo

## 1. Criando o Projeto no Supabase

### 1.1 Acesso ao Supabase

1. Acesse [supabase.com](https://supabase.com)
1. Clique em “Start your project”
1. Faça login com GitHub, Google ou email

### 1.2 Criação do Projeto

1. Clique em “New Project”
1. Selecione sua organização (ou crie uma nova)
1. Preencha os dados:
- **Project Name**: `sistema-pedidos` (ou nome de sua escolha)
- **Database Password**: Use uma senha forte (anote ela!)
- **Region**: Escolha a região mais próxima (ex: South America - São Paulo)
1. Clique em “Create new project”
1. Aguarde alguns minutos para o projeto ser criado

## 2. Executando os Scripts SQL

### 2.1 Acessando o SQL Editor

1. No painel do seu projeto, clique em “SQL Editor” no menu lateral
1. Clique em “New Query” para criar uma nova consulta

### 2.2 Script 1: Criação das Tabelas

Cole e execute este script:

```sql
-- SCRIPT 1: CRIAÇÃO DAS TABELAS
-- Execute primeiro este bloco

-- Criar tabela pedidos
CREATE TABLE pedidos (
id SERIAL PRIMARY KEY,
numero_pedido VARCHAR(20) UNIQUE NOT NULL,
cliente_id INTEGER,
data_pedido TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
valor_total DECIMAL(10,2) DEFAULT 0.00,
status VARCHAR(20) DEFAULT 'PENDENTE',
observacoes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Criar tabela pedidos_itens
CREATE TABLE pedidos_itens (
id SERIAL PRIMARY KEY,
pedido_id INTEGER NOT NULL,
produto_id INTEGER NOT NULL,
descricao_produto VARCHAR(255),
quantidade DECIMAL(10,3) DEFAULT 1.000,
valor_unitario DECIMAL(10,2) DEFAULT 0.00,
valor_total DECIMAL(10,2) DEFAULT 0.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

-- Chave estrangeira
CONSTRAINT fk_pedidos_itens_pedido
FOREIGN KEY (pedido_id) REFERENCES pedidos(id)
ON DELETE CASCADE
);
```

**Passos para executar:**

1. Cole o código acima no SQL Editor
1. Clique em “RUN” (ou Ctrl+Enter)
1. Verifique se aparece “Success. No rows returned” ou similar

### 2.3 Script 2: Criação dos Índices

Cole e execute este script em uma nova query:

```sql
-- SCRIPT 2: CRIAÇÃO DOS ÍNDICES
-- Execute após criar as tabelas

-- Índices para otimização da tabela pedidos
CREATE INDEX idx_pedidos_cliente ON pedidos(cliente_id);
CREATE INDEX idx_pedidos_data ON pedidos(data_pedido);
CREATE INDEX idx_pedidos_status ON pedidos(status);
CREATE INDEX idx_pedidos_numero ON pedidos(numero_pedido);

-- Índices para otimização da tabela pedidos_itens
CREATE INDEX idx_pedidos_itens_pedido ON pedidos_itens(pedido_id);
CREATE INDEX idx_pedidos_itens_produto ON pedidos_itens(produto_id);
```

### 2.4 Script 3: Função e Trigger para Cálculo Automático

Cole e execute este script:

```sql
-- SCRIPT 3: FUNÇÃO E TRIGGER PARA CÁLCULO AUTOMÁTICO
-- Execute após criar as tabelas e índices

-- Função para calcular valor total do pedido automaticamente
CREATE OR REPLACE FUNCTION atualizar_valor_total_pedido()
RETURNS TRIGGER AS $$
BEGIN
-- Atualiza o valor total do pedido baseado na soma dos itens
UPDATE pedidos
SET valor_total = (
SELECT COALESCE(SUM(valor_total), 0.00)
FROM pedidos_itens
WHERE pedido_id = COALESCE(NEW.pedido_id, OLD.pedido_id)
),
updated_at = CURRENT_TIMESTAMP
WHERE id = COALESCE(NEW.pedido_id, OLD.pedido_id);

RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

-- Trigger que executa a função automaticamente
CREATE TRIGGER trigger_atualizar_valor_pedido
AFTER INSERT OR UPDATE OR DELETE ON pedidos_itens
FOR EACH ROW
EXECUTE FUNCTION atualizar_valor_total_pedido();
```

### 2.5 Script 4: Dados de Teste (Opcional)

Para testar se tudo está funcionando:

```sql
-- SCRIPT 4: DADOS DE TESTE
-- Execute para inserir dados de exemplo

-- Inserir pedido de teste
INSERT INTO pedidos (numero_pedido, cliente_id, status, observacoes)
VALUES ('PED001', 1, 'PENDENTE', 'Pedido de teste');

-- Inserir itens de teste (pegue o ID do pedido criado)
INSERT INTO pedidos_itens (pedido_id, produto_id, descricao_produto, quantidade, valor_unitario, valor_total)
VALUES
(1, 101, 'Produto A', 2.000, 25.50, 51.00),
(1, 102, 'Produto B', 1.000, 15.75, 15.75),
(1, 103, 'Produto C', 3.500, 8.20, 28.70);

-- Verificar se o trigger funcionou (valor_total deve ser 95.45)
SELECT * FROM pedidos WHERE id = 1;
```

## 3. Verificação das Tabelas Criadas

### 3.1 Via Table Editor

1. Clique em “Table Editor” no menu lateral
1. Você deve ver as tabelas:
- `pedidos`
- `pedidos_itens`

### 3.2 Via SQL - Consulta de Verificação

```sql
-- Verificar estrutura das tabelas
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name IN ('pedidos', 'pedidos_itens')
ORDER BY table_name, ordinal_position;

-- Verificar triggers
SELECT trigger_name, event_manipulation, event_object_table
FROM information_schema.triggers
WHERE event_object_table IN ('pedidos', 'pedidos_itens');

-- Verificar índices
SELECT indexname, tablename, indexdef
FROM pg_indexes
WHERE tablename IN ('pedidos', 'pedidos_itens');
```

## 4. Configurar RLS (Row Level Security) - Opcional

Se você quiser controlar acesso aos dados:

```sql
-- SCRIPT 5: CONFIGURAÇÃO DE RLS (OPCIONAL)
-- Execute apenas se precisar de controle de acesso

-- Habilitar RLS
ALTER TABLE pedidos ENABLE ROW LEVEL SECURITY;
ALTER TABLE pedidos_itens ENABLE ROW LEVEL SECURITY;

-- Política para permitir tudo para usuários autenticados
CREATE POLICY "Permitir acesso completo para autenticados" ON pedidos
FOR ALL USING (auth.role() = 'authenticated');

CREATE POLICY "Permitir acesso completo para autenticados" ON pedidos_itens
FOR ALL USING (auth.role() = 'authenticated');

-- Política para permitir acesso anônimo (para desenvolvimento)
-- CUIDADO: Use apenas em desenvolvimento!
CREATE POLICY "Permitir acesso anônimo" ON pedidos
FOR ALL USING (true);

CREATE POLICY "Permitir acesso anônimo" ON pedidos_itens
FOR ALL USING (true);
```

## 5. Obter Chaves de API

### 5.1 Localizar as Chaves

1. No menu lateral, clique em “Settings”
1. Clique em “API”
1. Você verá:
- **Project URL**: `https://seuprojetoid.supabase.co`
- **anon/public key**: Para uso no frontend
- **service_role key**: Para operações admin (mantenha secreta!)

### 5.2 Anotar as Informações

```
Project URL: https://xxxxxxxxxxx.supabase.co
Anon Key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Service Role: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Database Password: [senha que você definiu]
```

## 6. Testar Conexão via API REST

### 6.1 Teste Simples

Use um cliente REST (Postman, Insomnia) ou curl:

```bash
# Listar pedidos
curl -X GET 'https://seuprojetoid.supabase.co/rest/v1/pedidos' \
-H "apikey: SUA_ANON_KEY" \
-H "Authorization: Bearer SUA_ANON_KEY"

# Inserir pedido
curl -X POST 'https://seuprojetoid.supabase.co/rest/v1/pedidos' \
-H "apikey: SUA_ANON_KEY" \
-H "Authorization: Bearer SUA_ANON_KEY" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{
"numero_pedido": "PED002",
"cliente_id": 2,
"status": "PENDENTE",
"observacoes": "Teste via API"
}'
```

## 7. Troubleshooting

### Problemas Comuns:

**Erro de permissão:**

- Verifique se RLS está desabilitado ou se as políticas estão corretas

**Erro de chave estrangeira:**

- Certifique-se que as tabelas foram criadas na ordem correta

**Função não encontrada:**

- Verifique se o script da função foi executado sem erros

**Trigger não funciona:**

- Teste inserindo um item e verificando se o valor_total do pedido é atualizado

### Comandos de Diagnóstico:

```sql
-- Ver todas as tabelas
\dt

-- Ver estrutura de uma tabela
\d pedidos

-- Ver triggers ativos
SELECT * FROM information_schema.triggers WHERE event_object_table = 'pedidos_itens';

-- Testar função manualmente
SELECT atualizar_valor_total_pedido();
```

Agora você tem sua base de dados configurada e pronta para integração com o Windev!



Client Use
#######

Unified WX Client for Supabase
Windev, Webdev, and Windev Mobile
1. Base Supabase Class (Shared)
Class: CSupabaseClient
CSupabaseClient is a Class
// Private properties
PRIVATE
m_sURL is string
m_sAnonKey is string
m_sServiceKey is string
m_sAuthToken is string
m_bDebugMode is boolean = False
END

// Public properties
PUBLIC
TimeoutHTTP is integer = 30000 // 30 seconds
MaxRetries is integer = 3
END
END

// Constructor
PROCEDURE Constructor(LOCAL sURL is string, LOCAL sAnonKey is string, LOCAL sServiceKey is string = "")
:m_sURL = sURL
:m_sAnonKey = sAnonKey
:m_sServiceKey = sServiceKey

// Initialization log
IF :m_bDebugMode THEN
LogWrite("Supabase Client initialized: " + sURL)
END
END

// Configure default headers
PROCEDURE PRIVATE ConfigureHeaders(bUseServiceKey is boolean = False)
HTTPReset()
HTTPAddHeader("Content-Type", "application/json")
HTTPAddHeader("Accept", "application/json")

IF bUseServiceKey AND Length(:m_sServiceKey) > 0 THEN
HTTPAddHeader("apikey", :m_sServiceKey)
HTTPAddHeader("Authorization", "Bearer " + :m_sServiceKey)
ELSE
HTTPAddHeader("apikey", :m_sAnonKey)
IF Length(:m_sAuthToken) > 0 THEN
HTTPAddHeader("Authorization", "Bearer " + :m_sAuthToken)
ELSE
HTTPAddHeader("Authorization", "Bearer " + :m_sAnonKey)
END
END

// Timeout
HTTPConfigure(httpTimeOut, :TimeoutHTTP)
END

// Generic request method
PROCEDURE PRIVATE MakeRequest(sMethod is string, sEndpoint is string, sBody is string = "", bUseServiceKey is boolean = False) : Variant
LOCAL
sURL is string
sResponse is string
vResult is Variant
nAttempts is integer = 0
nStatusHTTP is integer
sLogMsg is string
END

sURL = :m_sURL + sEndpoint

WHILE nAttempts < :MaxRetries
nAttempts++

:ConfigureHeaders(bUseServiceKey)

// Request log
IF :m_bDebugMode THEN
sLogMsg = StringFormat("[SUPABASE] %1 %2", sMethod, sURL)
IF Length(sBody) > 0 THEN
sLogMsg += " Body: " + sBody
END
LogWrite(sLogMsg)
END

// Make request based on method
CHOOSE sMethod
CASE "GET"
sResponse = HTTPRequest(httpGet, sURL)
CASE "POST"
sResponse = HTTPRequest(httpPost, sURL, sBody)
CASE "PATCH"
sResponse = HTTPRequest(httpPatch, sURL, sBody)
CASE "PUT"
sResponse = HTTPRequest(httpPut, sURL, sBody)
CASE "DELETE"
sResponse = HTTPRequest(httpDelete, sURL)
OTHER CASE
Error("Unsupported HTTP method: " + sMethod)
RETURN Null
END

nStatusHTTP = HTTPGetResult()

// Response log
IF :m_bDebugMode THEN
LogWrite(StringFormat("[SUPABASE] Response %1: %2", nStatusHTTP, Left(sResponse, 200)))
END

// Check for success
IF nStatusHTTP >= 200 AND nStatusHTTP <= 299 THEN
// Success - convert response to Variant
IF Length(sResponse) > 0 THEN
vResult = JSONToVariant(sResponse)
ELSE
vResult.success = True
vResult.statusCode = nStatusHTTP
END

vResult.statusCode = nStatusHTTP
RETURN vResult

ELSE IF nStatusHTTP >= 500 AND nAttempts < :MaxRetries THEN
// Server error - retry
IF :m_bDebugMode THEN
LogWrite(StringFormat("[SUPABASE] Server error %1, attempt %2/%3", nStatusHTTP, nAttempts, :MaxRetries))
END
Delay(1000 * nAttempts) // Exponential backoff
CONTINUE
ELSE
// Permanent error
vResult.error = True
vResult.statusCode = nStatusHTTP
vResult.message = sResponse
vResult.httpError = HTTPGetError()

IF :m_bDebugMode THEN
LogWrite(StringFormat("[SUPABASE] Permanent error %1: %2", nStatusHTTP, sResponse))
END

RETURN vResult
END
END

// Reached here, retries exhausted
vResult.error = True
vResult.message = "Maximum retries exceeded"
RETURN vResult
END

// Enable/disable debug
PROCEDURE EnableDebug(bEnable is boolean = True)
:m_bDebugMode = bEnable
END

// Set authentication token
PROCEDURE SetAuthToken(sToken is string)
:m_sAuthToken = sToken
END
2. Generic CRUD Operations Class
Class: CSupabaseTable
CSupabaseTable is a Class inherits from CSupabaseClient
PRIVATE
m_sTableName is string
END
END

// Constructor
PROCEDURE Constructor(LOCAL sURL is string, LOCAL sAnonKey is string, LOCAL sTableName is string, LOCAL sServiceKey is string = "")
Ancestor:Constructor(sURL, sAnonKey, sServiceKey)
:m_sTableName = sTableName
END

// SELECT - Fetch records
PROCEDURE Select(sColumns is string = "*", sFilter is string = "", sOrder is string = "", nLimit is integer = 0) : Variant
LOCAL
sEndpoint is string
sQuery is string = ""
END

sEndpoint = "/rest/v1/" + :m_sTableName

// Build query string
IF Length(sColumns) > 0 AND sColumns <> "*" THEN
sQuery = "select=" + sColumns
END

IF Length(sFilter) > 0 THEN
IF Length(sQuery) > 0 THEN sQuery += "&"
sQuery += sFilter
END

IF Length(sOrder) > 0 THEN
IF Length(sQuery) > 0 THEN sQuery += "&"
sQuery += "order=" + sOrder
END

IF nLimit > 0 THEN
IF Length(sQuery) > 0 THEN sQuery += "&"
sQuery += "limit=" + nLimit
END

IF Length(sQuery) > 0 THEN
sEndpoint += "?" + sQuery
END

RETURN :MakeRequest("GET", sEndpoint)
END

// INSERT - Insert record
PROCEDURE Insert(vData is Variant, bReturnData is boolean = True) : Variant
LOCAL
sEndpoint is string
sBody is string
END

sEndpoint = "/rest/v1/" + :m_sTableName

IF bReturnData THEN
HTTPAddHeader("Prefer", "return=representation")
END

sBody = VariantToJSON(vData)

RETURN :MakeRequest("POST", sEndpoint, sBody)
END

// UPDATE - Update records
PROCEDURE Update(vData is Variant, sFilter is string, bReturnData is boolean = False) : Variant
LOCAL
sEndpoint is string
sBody is string
END

sEndpoint = "/rest/v1/" + :m_sTableName

IF Length(sFilter) > 0 THEN
sEndpoint += "?" + sFilter
END

IF bReturnData THEN
HTTPAddHeader("Prefer", "return=representation")
END

sBody = VariantToJSON(vData)

RETURN :MakeRequest("PATCH", sEndpoint, sBody)
END

// DELETE - Delete records
PROCEDURE Delete(sFilter is string) : Variant
LOCAL
sEndpoint is string
END

sEndpoint = "/rest/v1/" + :m_sTableName

IF Length(sFilter) > 0 THEN
sEndpoint += "?" + sFilter
ELSE
Error("DELETE without filter is not allowed for safety")
RETURN Null
END

RETURN :MakeRequest("DELETE", sEndpoint)
END

// UPSERT - Insert or update
PROCEDURE Upsert(vData is Variant, sConflictColumn is string = "", bReturnData is boolean = True) : Variant
LOCAL
sEndpoint is string
sBody is string
END

sEndpoint = "/rest/v1/" + :m_sTableName

HTTPAddHeader("Prefer", "resolution=merge-duplicates")
IF bReturnData THEN
HTTPAddHeader("Prefer", "return=representation")
END

IF Length(sConflictColumn) > 0 THEN
sEndpoint += "?on_conflict=" + sConflictColumn
END

sBody = VariantToJSON(vData)

RETURN :MakeRequest("POST", sEndpoint, sBody)
END
3. Domain-Specific Classes
Class: COrderSupabase
COrderSupabase is a Class inherits from CSupabaseTable
END

// Constructor
PROCEDURE Constructor(LOCAL oSupabaseConfig is CSupabaseConfig)
Ancestor:Constructor(oSupabaseConfig.URL, oSupabaseConfig.AnonKey, "orders", oSupabaseConfig.ServiceKey)
END

// Fetch orders with specific filters
PROCEDURE FetchOrders(sStatus is string = "", nClientID is integer = 0, dhStartDate is DateTime = "", dhEndDate is DateTime = "") : Variant
LOCAL
sFilter is string = ""
arrFilters is array of 0 string
END

// Build filters
IF Length(sStatus) > 0 THEN
ArrayAdd(arrFilters, "status=eq." + sStatus)
END

IF nClientID > 0 THEN
ArrayAdd(arrFilters, "client_id=eq." + nClientID)
END

IF dhStartDate <> "" THEN
ArrayAdd(arrFilters, "order_date=gte." + DateTimeToString(dhStartDate, "YYYY-MM-DD"))
END

IF dhEndDate <> "" THEN
ArrayAdd(arrFilters, "order_date=lte." + DateTimeToString(dhEndDate, "YYYY-MM-DD"))
END

IF ArrayCount(arrFilters) > 0 THEN
sFilter = ArrayToString(arrFilters, "&")
END

RETURN :Select("*", sFilter, "order_date.desc")
END

// Create new order
PROCEDURE CreateOrder(sOrderNumber is string, nClientID is integer, sNotes is string = "") : Variant
LOCAL
vOrder is Variant
END

vOrder.order_number = sOrderNumber
vOrder.client_id = nClientID
vOrder.order_date = DateTimeToString(DateTimeSys(), "YYYY-MM-DD HH:mm:ss")
vOrder.status = "PENDING"

IF Length(sNotes) > 0 THEN
vOrder.notes = sNotes
END

RETURN :Insert(vOrder)
END

// Update order status
PROCEDURE UpdateStatus(nOrderID is integer, sNewStatus is string) : Variant
LOCAL
vData is Variant
END

vData.status = sNewStatus
vData.updated_at = DateTimeToString(DateTimeSys(), "YYYY-MM-DD HH:mm:ss")

RETURN :Update(vData, "id=eq." + nOrderID)
END
Class: COrderItemsSupabase
COrderItemsSupabase is a Class inherits from CSupabaseTable
END

// Constructor
PROCEDURE Constructor(LOCAL oSupabaseConfig is CSupabaseConfig)
Ancestor:Constructor(oSupabaseConfig.URL, oSupabaseConfig.AnonKey, "order_items", oSupabaseConfig.ServiceKey)
END

// Fetch items for an order
PROCEDURE FetchItemsByOrder(nOrderID is integer) : Variant
RETURN :Select("*", "order_id=eq." + nOrderID, "id")
END

// Add item to order
PROCEDURE AddItem(nOrderID is integer, nProductID is integer, sDescription is string, rQuantity is real, rUnitPrice is real) : Variant
LOCAL
vItem is Variant
END

vItem.order_id = nOrderID
vItem.product_id = nProductID
vItem.product_description = sDescription
vItem.quantity = rQuantity
vItem.unit_price = rUnitPrice
vItem.total_amount = rQuantity * rUnitPrice

RETURN :Insert(vItem)
END

// Update item quantity
PROCEDURE UpdateQuantity(nItemID is integer, rNewQuantity is real, rUnitPrice is real) : Variant
LOCAL
vData is Variant
END

vData.quantity = rNewQuantity
vData.total_amount = rNewQuantity * rUnitPrice

RETURN :Update(vData, "id=eq." + nItemID)
END

// Remove item from order
PROCEDURE RemoveItem(nItemID is integer) : Variant
RETURN :Delete("id=eq." + nItemID)
END
4. Configuration Class
Class: CSupabaseConfig
CSupabaseConfig is a Class
PUBLIC
URL is string
AnonKey is string
ServiceKey is string
END
END

// Constructor
PROCEDURE Constructor(LOCAL sURL is string, LOCAL sAnonKey is string, LOCAL sServiceKey is string = "")
:URL = sURL
:AnonKey = sAnonKey
:ServiceKey = sServiceKey
END

// Load configuration from INI file
PROCEDURE STATIC LoadFromINI(sFilePath is string) : CSupabaseConfig
LOCAL
oConfig is CSupabaseConfig
END

oConfig = new CSupabaseConfig()
oConfig.URL = INIRead("SUPABASE", "URL", "", sFilePath)
oConfig.AnonKey = INIRead("SUPABASE", "ANON_KEY", "", sFilePath)
oConfig.ServiceKey = INIRead("SUPABASE", "SERVICE_KEY", "", sFilePath)

RETURN oConfig
END
5. Usage Examples by Platform
5.1 WINDEV - Desktop Window
// Window global declarations
GLOBAL
goSupabaseConfig is CSupabaseConfig
goOrders is COrderSupabase
goItems is COrderItemsSupabase
END

// Window initialization
PROCEDURE InitializeSupabase()
// Load configuration
goSupabaseConfig = CSupabaseConfig::LoadFromINI(FExeDir() + "config.ini")

// Initialize clients
goOrders = new COrderSupabase(goSupabaseConfig)
goItems = new COrderItemsSupabase(goSupabaseConfig)

// Enable debug in development
goOrders.EnableDebug(True)
goItems.EnableDebug(True)
END

// Event: Click on Fetch Orders
PROCEDURE FetchOrders()
LOCAL
vResult is Variant
nI is integer
END

// Clear table
TableDeleteAll(TABLE_Orders)

// Fetch orders
vResult = goOrders.FetchOrders(COMBO_Status..Value, EDT_ClientID..Value)

IF NOT VariantTyped(vResult.error) OR vResult.error = False THEN
// Fill table
FOR nI = 1 TO ArrayCount(vResult)
TableAdd(TABLE_Orders, [
vResult[nI].id,
vResult[nI].order_number,
vResult[nI].client_id,
StringToDateTime(vResult[nI].order_date),
vResult[nI].total_amount,
vResult[nI].status
])
END

Info(StringFormat("%1 orders found", ArrayCount(vResult)))
ELSE
Error("Error fetching orders: " + vResult.message)
END
END

// Event: Double-click on orders table
PROCEDURE LoadOrderItems()
LOCAL
nOrderID is integer
vResult is Variant
nI is integer
END

nOrderID = TABLE_Orders[TABLE_Orders].[1] // Selected order ID

// Clear items table
TableDeleteAll(TABLE_Items)

// Load items
vResult = goItems.FetchItemsByOrder(nOrderID)

IF NOT VariantTyped(vResult.error) OR vResult.error = False THEN
FOR nI = 1 TO ArrayCount(vResult)
TableAdd(TABLE_Items, [
vResult[nI].id,
vResult[nI].product_id,
vResult[nI].product_description,
vResult[nI].quantity,
vResult[nI].unit_price,
vResult[nI].total_amount
])
END
ELSE
Error("Error loading items: " + vResult.message)
END
END
5.2 WEBDEV - Web Page
// Server code - Project initialization
PROCEDURE InitializeSupabaseWeb()
// Hardcoded or file-based configuration
goSupabaseConfig is CSupabaseConfig(
"https://yourprojectid.supabase.co",
"your-anon-key-here",
"your-service-key-here"
)

// Store in session
SessionWrite("supabase_config", goSupabaseConfig)
END

// Server code - Fetch orders AJAX
PROCEDURE FetchOrdersAJAX()
LOCAL
oConfig is CSupabaseConfig
oOrders is COrderSupabase
vResult is Variant
sJSON is string
END

// Retrieve configuration from session
oConfig = SessionRead("supabase_config")
oOrders = new COrderSupabase(oConfig)

// Fetch orders
vResult = oOrders.FetchOrders()

IF NOT VariantTyped(vResult.error) OR vResult.error = False THEN
sJSON = VariantToJSON(vResult)
AJAXExecuteAsync("DisplayOrders", sJSON)
ELSE
AJAXExecuteAsync("DisplayError", "Error: " + vResult.message)
END
END

// Browser code - Display orders
PROCEDURE DisplayOrders(sJSON is string)
LOCAL
vOrders is Variant
nI is integer
sHTML is string = ""
END

vOrders = JSONToVariant(sJSON)

FOR nI = 1 TO ArrayCount(vOrders)
sHTML += [

%2
%3
%4
%5
%6

]

sHTML = StringFormat(sHTML,
vOrders[nI].id,
vOrders[nI].order_number,
vOrders[nI].client_id,
vOrders[nI].order_date,
vOrders[nI].total_amount,
vOrders[nI].status
)
END

DIV_OrdersList..InnerHTML = sHTML
END
5.3 WINDEV MOBILE - Mobile Application
// Mobile project global declarations
GLOBAL
goSupabaseConfig is CSupabaseConfig
goOrders is COrderSupabase
goItems is COrderItemsSupabase
END

// Application initialization
PROCEDURE InitializeSupabaseMobile()
// Mobile configuration
goSupabaseConfig = new CSupabaseConfig(
"https://yourprojectid.supabase.co",
"your-anon-key-here"
)

goOrders = new COrderSupabase(goSupabaseConfig)
goItems = new COrderItemsSupabase(goSupabaseConfig)

// Shorter timeout for mobile
goOrders.TimeoutHTTP = 15000
goItems.TimeoutHTTP = 15000
END

// Window: Orders List
PROCEDURE LoadOrdersList()
LOCAL
vResult is Variant
nI is integer
END

// Show loading
WIN_Loading.Visible = True

// Thread to avoid blocking UI
ThreadExecute("ThreadFetchOrders", threadNormal)
END

// Thread to fetch orders
PROCEDURE ThreadFetchOrders()
LOCAL
vResult is Variant
nI is integer
sResultJSON is string
END

vResult = goOrders.FetchOrders()
sResultJSON = VariantToJSON(vResult)

// Return to main thread
ExecuteMainThread("ProcessOrdersResult", sResultJSON)
END

// Process result in main thread
PROCEDURE ProcessOrdersResult(sJSON is string)
LOCAL
vResult is Variant
nI is integer
END

// Hide loading
WIN_Loading.Visible = False

vResult = JSONToVariant(sJSON)

IF NOT VariantTyped(vResult.error) OR vResult.error = False THEN
// Clear list
ListDeleteAll(LIST_Orders)

// Fill list
FOR nI = 1 TO ArrayCount(vResult)
ListAdd(LIST_Orders,
StringFormat("%1 - Client: %2 - $ %3",
vResult[nI].order_number,
vResult[nI].client_id,
NumericToString(vResult[nI].total_amount, "999,999.99")
)
)

// Store order ID in stored value
LIST_Orders[LIST_Orders..Occurrence].StoredValue = vResult[nI].id
END
ELSE
Info("Error loading orders: " + vResult.message)
END
END
6. Configuration File (config.ini)
[SUPABASE]
URL=https://yourprojectid.supabase.co
ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIs...
SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIs...

[HTTP]
TIMEOUT=30000
MAX_RETRIES=3
DEBUG=1
7. Additional Utilities
Class: CSupabaseUtils
CSupabaseUtils is a Class
END

// Convert DateTime to Supabase format
PROCEDURE STATIC DateTimeToSupabase(dhDate is DateTime) : string
RETURN DateTimeToString(dhDate, "YYYY-MM-DD HH:mm:ss.CCC+00:00")
END

// Convert Supabase string to DateTime
PROCEDURE STATIC SupabaseToDateTime(sDate is string) : DateTime
// Remove timezone if present
sDate = StringReplace(sDate, "+00:00", "")
sDate = StringReplace(sDate, "T", " ")

RETURN StringToDateTime(sDate)
END

// Escape string for filters
PROCEDURE STATIC EscapeString(sValue is string) : string
sValue = StringReplace(sValue, "'", "''")
sValue = StringReplace(sValue, "\", "\\")
RETURN sValue
END

// Build text search filter
PROCEDURE STATIC TextFilter(sColumn is string, sValue is string, sOperator is string = "ilike") : string
IF sOperator = "ilike" THEN
RETURN sColumn + "=ilike.*" + EscapeString(sValue) + "*"
ELSE
RETURN sColumn + "=" + sOperator + "." + EscapeString(sValue)
END
END

This unified WX client enables consistent use of Supabase across all WX platforms, with error handling, automatic retries, debug capabilities, and platform-specific examples.

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/
Registered member
4,618 messages
Posted on September, 05 2025 - 11:06 AM
Introdução

Em aplicações empresariais avançadas, como as desenvolvidas com WinDev, a atualização instantânea de dados críticos (pedidos, estoques, status) torna-se um problema de responsividade para o usuário. O acoplador Supabase Realtime com uma interface WinDev permite uma resposta eficiente a essa resposta: as alterações no banco de dados Postgres são imediatamente propagadas pela interface, sem consulta, com latência praticamente nula.
Este artigo explora os comentários incluídos nessa arquitetura, após a origem da nova integração Postgres no código WinDev via WebSocketConnectSSL.

Apresentação Rápida do Supabase Realtime
O Supabase Realtime é um componente da plataforma Supabase que permite receber eventos (INSERT, UPDATE, DELETE) para uma base PostgreSQL, utilizando funções de Replicação Lógica e o WAL (Write-Ahead Logging) do PostgreSQL.
Configurar a replicação lógica para o serviço Realtime do Supabase também permite capturar todas as alterações em uma tabela para transmissão aos clientes existentes via WebSocket.

Formato Phoenix

O Supabase Realtime utiliza Phoenix Channels, um protocolo de comunicação baseado em JSON usado no ecossistema Elixir. Cada mensagem WebSocket segue um formato estruturado:

{
"topic": "realtime:public:command",
"event": "INSERT",
"payload": {
"new": {
"id": 4521,
"client": "IsiNeva",
"amount": 1250
},
"old": null,
"type": "INSERT"
},
"ref": null,
"join_ref": "1"
}

Conexão WebSocket Segura com o WinDev
O WinDev possui a função WebSocketConnectSSL, ideal para estabelecer uma conexão segura com o Supabase. Exemplo de conexão WebSocket


sURL is this link = "wss://xyz.supabase.co/realtime/v1/websocket?apikey=xxxx"
SE WebSocketConnectSSL("SupabaseSocket", sURL) ENTÃO
Trace("Conexão dos Réus")
CASO CONTRÁRIO
Error("Verificação de conexão: " + ErrorInfo())
FIM


A autenticação depende de um token de API (chave do projeto ou configuração de selo JWT), que é transmitido no parâmetro da solicitação.

Gerenciamento de Mensagens no WinDev
Uma vez conectado, você deve:

Entrar em um canal Phoenix (com join)
Requisitar uma tabela específica (ex.: public.command)
Processar eventos INSERT, UPDATE e DELETE
Enviar uma mensagem de "join" no canal


sJoinMsg est une chaîne = [
{
"topic": "realtime:public:commande",
"event": "phx_join",
"payload": {},
"ref": "1"
}
]
WebSocketEnvoie("SupabaseSocket", sJoinMsg)

Ou:

WebSocketSend("SupabaseSocket", sJoinMsg)

Receber e analisar mensagens

PROCÉDURE WS_SupabaseSocket_MessageReçue(sMessage est une chaîne)
JSON est un Variant = JSONVersVariant(sMessage)
SELON JSON.event
CAS "INSERT":
id est un entier = JSON.payload.new.id
client est une chaîne = JSON.payload.new.client
Trace("Nouvelle commande : " + client + " (" + id + ")")
FIN

Benefícios para o seu negócio

A adoção do Supabase Realtime proporciona à arquitetura WinDev benefícios mais tangíveis:
Redução de pesquisas: não há necessidade de consultar o banco de dados em intervalos fixos.
Responsividade da interface: os usuários veem as alterações imediatamente.
Simplicidade de implantação: do envio de notificações à manutenção.
Evolução: mais clientes WinDev podem se conectar à mesma instância do Supabase.

Caso concreto: atualização de um painel.
Quando o aplicativo é criado para o IsiNeva, você pode adicionar, modificar ou excluir um pedido, o que é refletido instantaneamente na interface do WinDev sem a necessidade de ação do usuário. O painel atual e a lista de pedidos também são acionados no retorno de chamada WS_SupabaseSocket_MessageReceived, o que é suficiente para garantir uma interface consistente em tempo real.

Conclusão

A integração do Supabase Realtime com o WinDev via WebSocketConnectSSL é uma solução moderna, eficiente e rápida para interfaces de negócios dinâmicas. Ela transforma um aplicativo clássico em um sistema interativo, sincronizado com seus servidores.

Na IsiNeva, essa abordagem permite que você libere suas ferramentas ultra-responsáveis, adaptando-se às demandas do terreno ou aos métodos de back-office, sem aumentar a carga do servidor ou da infraestrutura complexa.

Links externos

Documentação do Supabase Realtime
https://supabase.com/docs/guides/realtime

Protocolo Phoenix Channels
https://hexdocs.pm/phoenix/channels.html

WebSocketConnectSSL – Ajuda do PCSoft
https://doc.pcsoft.fr/?1000021470

Websockets para colaboração em tempo real e trabalho (vídeo do PCSoft no YouTube)


Poc Windev Realtime GitHub
https://github.com/LG-IsiNeva/Poc_Windev_Realtime

--
Adriano José Boller
______________________________________________
Consultor e Representante Oficial da
PcSoft no Brasil
+55 (41) 99949 1800
adrianoboller@gmail.com
skype: adrianoboller
http://wxinformatica.com.br/