O modelo de dados
Criar a base de dados da app, as três tabelas do CRM e o modelo com as relações que o resto da plataforma vai usar.
A app Gestão de Clientes existe e está vazia. Esta etapa dá-lhe o
alicerce: a base de dados onde os registos vivem, as tabelas contas,
contactos e oportunidades, e o modelo que liga tudo — o mapa que
as APIs, os ecrãs e os scripts vão ler daqui em diante.
No fim desta página tens dados a sério: três tabelas criadas, ligadas entre si, e uma consola onde as consultas devolvem linhas.
Duas camadas, e vale a pena não as confundir
O Keplin trabalha com dados em duas camadas sobrepostas. Elas fazem coisas diferentes e mexem-se em sítios diferentes:
| Camada | O que é | Onde se mexe |
|---|---|---|
| Datasource | A base de dados em si — a ligação, as tabelas, as colunas, as linhas. | Painel Dados ▸ Fontes de dados |
| Modelo | O retrato dessa base de dados dentro da plataforma: entidades, campos com nomes amigáveis e relações. | O separador do datasource, no canvas do modelo |
A distinção é prática. Criar uma coluna mexe na base de dados. Importar uma tabela para o modelo não mexe em nada na base de dados — só diz à plataforma "esta tabela interessa-me, e é assim que ela se lê". É o modelo que alimenta a API GraphQL da app, as APIs de tabela e, por elas, os ecrãs.
Nota
Neste guia a base de dados é criada de raiz, dentro da app. Se a tua organização já tem uma base de dados com os clientes lá dentro, o caminho é o mesmo a partir do passo "Importar as tabelas para o modelo" — regista a ligação e importa as tabelas que existem. O capítulo Ligar bases de dados trata desse caso.
Criar o datasource Dados CRM
O primeiro passo é registar a base de dados da app. Como não vamos ligar nada externo, usamos o tipo que a plataforma cria e guarda com a própria app: não pede servidor, porto, utilizador nem password.
No espaço de trabalho da app, escolhe o painel Dados na base da barra lateral.
Na secção Fontes de dados, clica no botão + (Novo datasource). Abre o diálogo Novo datasource — "Liga uma base de dados a esta app. Tudo é cifrado em repouso."
Em Nome interno, escreve
Dados CRM. É por este nome — exactamente este — que as APIs e os scripts se vão referir à ligação mais à frente no guia.Abre a lista Tipo. Ela mostra todos os motores suportados; escolhe o da base de dados local, a que fica guardada com a app. Repara no que acontece a seguir: os campos de servidor, porto, utilizador e password desaparecem — não há nada para ligar.

A lista de tipos de base de dados no diálogo Novo datasource: os seis motores suportados. Sobra um campo, Importar base de dados (opcional). Deixa-o vazio: "Sem ficheiro, é criada uma base de dados vazia." É o que queremos.
Clica em Testar ligação para confirmar — a resposta é Ligação OK.
Clica em Criar. O datasource aparece na árvore e o separador dele abre logo, com o canvas do modelo — ainda vazio.

Atenção
O Nome interno é um identificador, não um rótulo. Mudá-lo mais
tarde obriga a rever os scripts que chamam db("Dados CRM") e os
passos SQL que escolheram a ligação pelo nome antigo.
Criar a tabela contas
Com o datasource criado, as tabelas fazem-se sem sair da plataforma.
- Na árvore, abre o menu ⋯ do datasource Dados CRM e escolhe Nova tabela.
- Em Nome da tabela, escreve
contas. Deixa Schema (opcional) em branco. - Em Descrição da tabela, escreve
Empresas clientes e potenciais clientes. É opcional, mas é o que vais ler daqui a um ano. - A lista COLUNAS já traz uma coluna
id, do tipointeger, com Chave primária (PK) e Auto-incremento ligados. Deixa-a como está — é a identidade de cada registo. - Clica no + de COLUNAS para cada coluna nova e preenche Nome, Tipo e os interruptores. A tabela abaixo diz o que escrever.
- Confirma em Criar tabela. A tabela nasce na base de dados e passa a aparecer na árvore de objectos.

As colunas da tabela contas:
| Coluna | Tipo | Permite NULL | Para que serve |
|---|---|---|---|
id |
integer |
não | Chave primária, com auto-incremento |
nome |
text |
não | O nome da empresa |
nif |
text |
sim | Número de contribuinte |
sector |
text |
sim | Agroalimentar, Tecnologia, Saúde… |
cidade |
text |
sim | Onde a empresa está |
telefone |
text |
sim | Contacto geral |
email |
text |
sim | Contacto geral |
estado |
text |
não | ativo, prospeto ou inativo |
Dica
Permite NULL desligado quer dizer obrigatório na base de dados. Reserva-o para o que é mesmo obrigatório — o nome de uma empresa, a conta a que um contacto pertence. Um campo que hoje é opcional e amanhã obrigatório muda-se num instante; o contrário obriga a limpar dados.
Criar as tabelas contactos e oportunidades
Repete o gesto — menu ⋯ do datasource ▸ Nova tabela — mais duas vezes.
contactos (descrição: Pessoas de contacto de cada conta):
| Coluna | Tipo | Permite NULL | Para que serve |
|---|---|---|---|
id |
integer |
não | Chave primária, com auto-incremento |
nome |
text |
não | Nome da pessoa |
cargo |
text |
sim | Director Geral, Responsável de Compras… |
email |
text |
sim | |
telefone |
text |
sim | |
conta_id |
integer |
não | A conta a que a pessoa pertence |
oportunidades (descrição: Negócios em curso, por fase):
| Coluna | Tipo | Permite NULL | Para que serve |
|---|---|---|---|
id |
integer |
não | Chave primária, com auto-incremento |
titulo |
text |
não | O nome do negócio |
conta_id |
integer |
não | A conta do negócio |
valor |
real |
sim | Valor em euros — número com decimais |
fase |
text |
não | A fase do negócio (ver abaixo) |
data_fecho |
text |
sim | Data prevista de fecho, em AAAA-MM-DD |
responsavel |
text |
sim | Quem acompanha o negócio |
A coluna fase é uma lista fechada de valores. Guarda-se como texto,
e os valores possíveis são sempre estes seis:
| Valor guardado | O que significa |
|---|---|
prospecao |
Ainda não houve conversa a sério |
qualificacao |
Há interesse e estamos a perceber o encaixe |
proposta |
Proposta entregue |
negociacao |
A discutir condições |
fechada_ganha |
Negócio fechado |
fechada_perdida |
Negócio perdido |
Nota
Guardamos o valor "técnico" (fechada_ganha) e mostramos a etiqueta
bonita ("Ganha") no ecrã. É essa separação que faz o quadro kanban da
etapa seguinte funcionar: cada coluna do quadro é um destes valores,
com a sua etiqueta e a sua cor. As colunas estado (das contas) e
fase seguem a mesma ideia.
Rever e alterar a estrutura de uma tabela
Enganaste-te num tipo, faltou uma coluna, o nome não é o melhor. Nada disso é definitivo:
- Na árvore, abre o menu ⋯ da tabela e escolhe Editar estrutura.
- O diálogo tem dois separadores: Colunas e Índices. No topo ficam o Nome na BD, o Nome amigável (apps) — o nome que os ecrãs vão mostrar — e a descrição.
- Clica numa coluna à esquerda para a editar à direita, ou usa o + para acrescentar. Colunas novas ficam marcadas "Coluna nova — é criada ao aplicar as alterações"; colunas apagadas ficam "Marcada para eliminar (DROP) ao aplicar", e a eliminação anula-se enquanto não aplicares.
- Clica em Aplicar alterações.

Atenção
Eliminar uma coluna elimina os dados dela. A plataforma só executa a alteração quando carregas em Aplicar alterações — até lá é tudo rascunho, e fechar o diálogo não estraga nada.
Ver e semear os dados
A árvore de objectos tem uma consola por baixo do modelo, e é por ela que se espreitam (ou se semeiam) os dados:
- Abre o menu ⋯ de uma tabela e escolhe Ver dados. A Consola
SQL abre em baixo, já com um
selectpronto para essa tabela. - Clica em Executar. Os resultados aparecem à direita, com o número de linhas e um campo Filtrar….
- Para meter as primeiras linhas, escreve os
insertque quiseres na consola e executa. É a forma mais rápida de ter dados de exemplo antes de haver ecrãs para os criar.

Importar as tabelas para o modelo
As tabelas existem, mas a plataforma ainda não sabe que as quer usar. É o que a importação faz:
- Na árvore, expande Dados CRM ▸ Tabelas. Estão lá as três.
- Para cada uma, abre o menu ⋯ e escolhe Importar p/ modelo — ou arrasta a tabela da árvore para o canvas do modelo, que dá no mesmo.
- Cada tabela vira um cartão no canvas: a entidade. O cartão mostra os campos, o tipo de cada um e a marca PK na chave primária.

Os nomes das entidades ficam em maiúscula inicial — contas vira
Contas — porque é assim que aparecem nas APIs e nos ecrãs. A tabela
na base de dados continua a chamar-se contas.
Nota
Importar não copia dados nem cria nada na base de dados. E remover uma entidade do modelo também não apaga a tabela — "NÃO altera a tabela na base de dados", como o próprio aviso diz.
Ligar as entidades — as duas relações
Um CRM sem relações é três listas soltas. Faltam duas ligações: cada contacto pertence a uma conta, cada oportunidade pertence a uma conta.
Para criar uma relação, arrasta o campo conta_id da entidade
Contactos para o campo id da entidade Contas — a dica no cartão
lembra-o: "Arrasta para um campo de outra tabela para ligar". Abre o
diálogo Nova relação, já com as entidades e as colunas preenchidas:
| Campo | O que escolher | Porquê |
|---|---|---|
| Cardinalidade | One-to-many (1:N) | Uma conta tem muitos contactos; cada contacto tem uma conta. |
| Parent (referenciada) | Contas ▸ id |
O lado "um". |
| Child (tem a FK) | Contactos ▸ conta_id |
O lado "muitos" — é ele que guarda a referência. |
| Tipo de relação | Física — cria a FK na base de dados | A base de dados passa a garantir que não há contactos órfãos. |
| Navigator em Contactos → Contas | conta |
O campo virtual que, a partir de um contacto, dá a conta dele. |
| Navigator em Contas → Contactos | contactos |
O campo virtual que, a partir de uma conta, dá os contactos dela. |
| Ao apagar o pai (ON DELETE) | Nada (bloqueia se houver filhos) | Apagar uma conta com contactos passa a ser recusado — melhor um erro que um buraco. |
Confirma em Criar relação e repete o gesto entre Oportunidades ▸
conta_id e Contas ▸ id, com o navigator inverso oportunidades.

Os navigators são a parte que mais rende. São campos que não existem
na base de dados mas existem no modelo: com eles, uma consulta de
oportunidades devolve conta.nome sem que ninguém escreva um join. É
exactamente isso que a tabela do dashboard vai fazer na etapa seguinte,
na coluna Conta.
Dica
Física cria mesmo a chave estrangeira na base de dados; Virtual — só no modelo da plataforma serve para bases de dados onde não podes (ou não queres) mexer no esquema. Neste guia a base é nossa, por isso física.
O que ficou desbloqueado
Com o modelo pronto, a app ganhou coisas de graça:
- A API GraphQL da app já conhece Contas, Contactos e Oportunidades, com as relações — ver A API GraphQL do modelo.
- As APIs de tabela podem agora apontar a uma entidade e gerar leitura e escrita sem uma linha de SQL. É o primeiro passo da etapa seguinte.
- Os ecrãs vão ler destas APIs através de datastores.
Porque não…?
- Porque não aparece a minha tabela na árvore? A árvore de objectos é lida da base de dados — usa Actualizar objectos no menu ⋯ do datasource depois de mexeres fora da plataforma.
- Porque não consigo criar a relação? As duas colunas têm de ser
compatíveis: uma chave primária
integerliga-se a uminteger. Se arrastaste para o campo errado, cancela e repete — o diálogo diz que falta escolher as colunas. - Porque é que o campo
contanão aparece nos meus dados? Os navigators não são colunas: só existem através do modelo. Se estás a consultar pela Consola SQL vês as colunas reais; é nas APIs e nos ecrãs que os navigators aparecem. - Porque é que a plataforma não me deixa apagar uma conta? Escolheste Nada (bloqueia se houver filhos) no ON DELETE — e há contactos ou oportunidades a apontar para ela. Apaga-os primeiro, ou muda a regra da relação.
O alicerce está feito. Próxima etapa: os ecrãs.