Relações: o histórico de interações

Começa hoje a segunda parte do módulo: as funcionalidades de gestão. Cada cliente passa a ter uma ficha com o histórico de chamadas, emails, reuniões e visitas — e, para isso, vais ligar duas tabelas com uma chave estrangeira.

🎯 No fim desta aula vais conseguir

🧰 Antes de começar

  1. Abre a pasta meu-crm no VS Code e o index.html com o Live Server.
  2. Confirma que tens pelo menos 3 clientes na tabela. Se não tiveres, cria-os agora (inventa nomes de clientes do teu setor).
  3. Abre a Consola (F12) e confirma que não há erros vermelhos.

😕 O teu projeto não funciona ou faltaste às últimas aulas? Descarrega o ponto de controlo da aula 12, descompacta-o e trabalha a partir dele. Repara que o ponto de controlo não tem o campo Cidade da ficha da aula 12: se o teu projeto funciona, continua com o teu.

🧭 A segunda parte numa frase

Na primeira parte aprendeste a guardar, editar, apagar e pesquisar clientes. Agora vais transformar o CRM numa verdadeira aplicação de gestão. Até à aula 16 acrescentas as peças que (quase) todas as aplicações de gestão têm; nas aulas 17 a 20 adaptas o CRM ao teu setor, testas, publicas e apresentas.

AulaO que acrescentas ao CRMPergunta de negócio a que responde
13 (hoje)Histórico de interações de cada cliente«O que é que já falámos com este cliente?»
14Oportunidades de negócio por fases«Que negócios temos em curso e em que ponto estão?»
15Painel com indicadores«Como está a correr o negócio?»
16Relatórios para Excel e cópias de segurança«Consigo tirar os dados daqui e não os perder?»

Parte A — A ideia: duas tabelas ligadas (≈ 15 min)

💡 Porque é que um CRM precisa de um histórico?

Imagina que trabalhas numa empresa e o telefone toca: é a Ana Silva. Antes de falar com ela, querias saber: «Quando foi a última vez que falámos? O que lhe prometemos?» Sem histórico, cada colega começa do zero e o cliente sente que ninguém se lembra dele. Por isso, todos os CRM guardam as interações: cada chamada, email, reunião ou visita.

Analogia do escritório: antigamente havia um arquivador com uma ficha por cliente, cada uma com um número (o nº de cliente). Ao lado do telefone havia um caderno de contactos, onde se escrevia uma linha por cada contacto: «3 de outubro — Chamada — cliente nº 17 — pediu orçamento». No caderno não se copiava a morada nem o email do cliente: escrevia-se só o número. Quem quisesse saber mais ia ao arquivador procurar a ficha nº 17.

No nosso CRM vai ser igual: a tabela clientes é o arquivador e a nova tabela interacoes é o caderno.

Tabela clientes (o arquivador)

idnomeempresa
17Ana SilvaPadaria Silva
23Rui CostaOficina Costa

Tabela interacoes (o caderno)

idclienteIddatatipo
101172026-10-01Chamada
102232026-10-02Email
103172026-10-03Reunião

💡 No teu CRM os id não são 17 ou 23, são números enormes como 1759486412345 (vêm do Date.now(), lembras-te da aula 8?). Aqui usamos números pequenos para ser mais fácil de ler.

Duas palavras novas, muito usadas em bases de dados:

Porque guardamos só o número, e não o nome do cliente? (1) Se a Ana mudar de email, só se corrige num sítio; (2) pode haver duas clientes chamadas «Ana Silva», mas nunca dois clientes com o mesmo id; (3) um número ocupa pouco espaço. É assim que funcionam as bases de dados relacionais que viste na aula 11.

Em SQL, mostrar o histórico da Ana seria assim (não é para colar, é só para comparar):

Hoje vais fazer exatamente isto, mas em JavaScript.

🤔 Pensa: se a ficha nº 17 for deitada fora do arquivador, o que acontece às linhas do caderno que dizem «cliente nº 17»? Escreve a tua resposta no caderno — vais investigá-lo no fim da aula.

Parte B — A ficha do cliente com o histórico (≈ 45 min)

1 A ficha do cliente (HTML)

Primeiro, a parte que se vê: uma secção nova com o nome do cliente, um pequeno formulário para registar interações e uma lista vazia, onde o JavaScript vai escrever o histórico. Repara em três coisas:

No index.html, procura o comentário AULA 13: COLAR AQUI A FICHA DO CLIENTE. Fica depois do </section> que fecha a secção dos clientes — não coles a ficha dentro da secção dos clientes.

🤔 Prevê: grava e olha para o CRM no browser. O que mudou?

✅ Deves ver: nada de novo! A ficha existe, mas está escondida por causa do hidden. Para confirmares que está lá: botão direito → Inspecionar e procura <section id="detalhe" class="cartao" hidden> no separador Elements.

2 As funções da ficha (JavaScript)

⚠️ Atenção ao sítio: no app.js há dois comentários com «AULA 13: COLAR AQUI»: um pequeno, dentro da função atualizarEcra() (é para o passo 4), e um grande, com =====, perto do fim do ficheiro. Este bloco vai para o grande: // ===== AULA 13: COLAR AQUI =====. Usa Ctrl+F para o encontrar.

O bloco é comprido, mas cada função faz uma coisa simples:

💡 Na ordenação, (a, b) => b.data.localeCompare(a.data) tem o b primeiro: é por isso que fica do mais recente para o mais antigo (lembras-te do «Nome Z → A» da aula 10?).

✅ Deves ver: ainda nada de novo no ecrã, e nenhum erro na Consola. Experimenta escrever na Consola interacoes (deve aparecer [], uma lista vazia) e clienteAberto (deve aparecer null).

3 O botão 👁️ «Ver ficha»

Agora falta uma maneira de abrir a ficha. Na função linhaCliente(), dentro da célula das ações, está o comentário <!-- AULA 13: COLAR AQUI O BOTÃO VER --> que deixaste ficar na aula 9. Cola o botão por baixo desse comentário (fica antes do botão ✏️).

Este botão é HTML escrito dentro do JavaScript (dentro das crases ` `). O ${cliente.id} é trocado pelo id de cada cliente, por isso cada botão sabe qual é a ficha que deve abrir.

✅ Deves ver: cada linha da tabela tem agora um botão 👁️. Ao clicares, aparece a ficha por baixo da tabela, com o nome do cliente, a empresa, o email, o telefone e o estado, a data de hoje no calendário e a frase «Ainda não há interações com este cliente.»

😕 Não aparece o botão / aparece um erro
  • Erro Unexpected token '<': colaste o botão fora das crases. Tem de ficar entre <td class="acoes"> e </td>, junto aos botões ✏️ e 🗑️.
  • Ao clicar: verCliente is not defined → o bloco do passo 2 não foi gravado, ou foi colado dentro da função atualizarEcra() (no comentário pequeno). Muda-o para o comentário grande.
  • Ao clicar: Cannot set properties of null (setting 'textContent') → falta a ficha do passo 1, ou colaste-a no ficheiro errado (tem de ir para o index.html).
  • Ao clicar: procurarCliente is not defined → o teu projeto não tem a aula 9. Usa o ponto de controlo da aula 12.

4 Atualizar a ficha sempre que os dados mudam

As funções guardarInteracao() e apagarInteracao() terminam com atualizarEcra(), a função que volta a desenhar tudo (aula 7). Falta dizer-lhe que também tem de desenhar o histórico. Cola esta linha dentro da função atualizarEcra(), por baixo do comentário pequeno // AULA 13: COLAR AQUI.

🤔 Prevê: se te esquecesses desta linha, o que acontecia quando registasses uma interação?

Confere: a tua função deve ficar assim.

💡 Resposta ao «prevê»: a interação ficava guardada, mas só a vias depois de fechar e voltar a abrir a ficha.

5 Testar

  1. Grava todos os ficheiros e clica no 👁️ de um cliente.
  2. Regista uma interação: tipo Chamada, descrição «Pediu orçamento», com a data de hoje.
  3. Regista uma segunda interação, mas muda a data para a semana passada. Deve aparecer por baixo da primeira (a mais recente fica em cima).
  4. Tenta registar uma interação sem descrição: deve aparecer o aviso «Escreve uma descrição da interação.»
  5. Carrega em F5, volta a abrir a ficha do mesmo cliente: as interações continuam lá.
  6. Abre a ficha de outro cliente: não deve ter nenhuma das interações do primeiro.
  7. Apaga uma interação com o 🗑️ (experimenta primeiro Cancelar, depois OK). Por fim, clica em Fechar ✖.

✅ Deves ver: cada cliente com o seu histórico, da data mais recente para a mais antiga; os dados continuam lá depois de recarregar a página.

😕 Não funciona?
  • Registas a interação mas a lista não muda (só aparece se fechares e abrires a ficha) → falta o passo 4.
  • Cannot access 'interacoes' before initialization (ou 'clienteAberto') → colaste o bloco do passo 2 por baixo da linha de arranque atualizarEcra();, ou colaste a linha do passo 4 fora da função. A linha de arranque tem de ser sempre a última do ficheiro.
  • Identifier 'interacoes' has already been declared → colaste o bloco do passo 2 duas vezes. Apaga uma das cópias.
  • A ficha aparece no meio da secção dos clientes → no passo 1 colaste antes do </section> dos clientes. Corta e cola no sítio certo.
  • Carregas em «Registar interação» e não acontece nada → abre a Consola e lê o erro: guardarInteracao is not defined quer dizer que o passo 2 não está no sítio certo.

🔎 Investiga

  1. No DevTools, abre o separador Aplicação (Application) → Armazenamento local → o endereço do Live Server. Clica em crm_interacoes e depois em crm_clientes. Compara o clienteId de uma interação com o id do cliente a quem a registaste. São iguais? É isto uma chave estrangeira.
  2. Cria um cliente «Teste Apagar», regista-lhe 2 interações e depois apaga o cliente. Volta a crm_interacoes: as interações dele ainda lá estão? A que cliente pertencem agora? (Chama-se a isto registos órfãos — o desafio ⭐ resolve o problema.)
  3. Com uma ficha aberta, escreve clienteAberto na Consola. Fecha a ficha e escreve outra vez. O que mudou? Porquê?
  4. Porque guardamos a data como 2026-10-03 e não como 03/10/2026? Ordena «como se fossem palavras» (letra a letra, da esquerda para a direita) estas três datas: 03/10/2026, 28/09/2026, 15/01/2027. A ordem fica certa? E com 2026-10-03, 2026-09-28, 2027-01-15?

✍️ Exercícios (fazem parte do teu projeto)

Estes exercícios não têm o código pronto: usa o que aprendeste hoje e nas aulas anteriores.

E1 · Um ícone para cada tipo de interação

No histórico, antes do tipo de interação, mostra um ícone: 📞 Chamada, ✉️ Email, 🤝 Reunião e 🚗 Visita.

💡 Dica: cria uma função (por exemplo iconeTipo(tipo)) que recebe o tipo e devolve (return) o ícone certo, usando if / else if (aula 5). Depois, em desenharInteracoes(), procura a linha que mostra ${interacao.tipo} e chama lá a tua função com ${…}. Podes copiar os ícones desta página.

E2 · Contador no título

O título do histórico deve mostrar quantas interações tem o cliente aberto, por exemplo «Histórico de interações (3)». O número tem de mudar quando registas ou apagas uma interação.

💡 Dica: no index.html, põe um <span id="…"> dentro do <h3>, à volta do número. Em desenharInteracoes(), há uma lista que tem só as interações deste cliente — qual é? Que propriedade de um array diz quantos elementos ele tem (aula 7)? Usa textContent para escrever o número.

⭐ Desafio · Apagar em cascata

Quando apagas um cliente, apaga também todas as interações dele (para não ficarem órfãs). Se a ficha desse cliente estiver aberta, fecha-a.

💡 Dica: em apagarCliente(), depois de guardar os clientes, cria uma lista nova (por exemplo restantes) e, com um ciclo, põe lá só as interações cujo clienteId é diferente (!==) do id apagado. Depois troca a lista interacoes pela nova e grava-a com guardar(…). Nas bases de dados a sério isto chama-se ON DELETE CASCADE.

📦 O que mostrar ao professor

⏱️ Se sobrar tempo

🤖 Pedir ajuda à IA (opcional)

Se não percebeste bem a ideia de tabelas relacionadas, pede a uma IA gratuita que te explique com um exemplo do teu setor. Copia este pedido, troca o texto entre [ ] e cola-o na IA:

💡 Se for um erro vermelho na Consola, usa o pedido da aula 7: cola o erro e pede uma explicação, sem a solução.

📖 Palavras novas

Interação
Cada contacto com um cliente: chamada, email, reunião, visita…
Relação 1 para N
Ligação entre duas tabelas em que um registo de uma (um cliente) corresponde a muitos registos da outra (muitas interações).
Chave primária
O campo que identifica cada registo sem repetições (o nosso id).
Chave estrangeira
Campo que guarda a chave primária de outra tabela, para ligar os registos (o nosso clienteId).
Registo órfão
Registo cuja chave estrangeira aponta para algo que já não existe (ex.: interação de um cliente apagado).
Apagar em cascata
Quando se apaga um registo, apagam-se também os registos que dependem dele.
null
Valor que quer dizer «nada» ou «nenhum», de propósito.
hidden
Atributo HTML que esconde um elemento. Em JavaScript: elemento.hidden = true esconde, false mostra.
Ordem descendente (DESC)
Do maior para o menor (nas datas: da mais recente para a mais antiga).