Oportunidades de negócio por fases
O CRM passa a acompanhar os negócios em curso num quadro com colunas — o funil de vendas — e tu fazes cada negócio avançar de fase com um clique.
🎯 No fim desta aula vais conseguir
- Explicar o que é um funil de vendas (pipeline) e um quadro kanban.
- Criar uma segunda tabela ligada aos clientes: as oportunidades, cada uma com o seu
clienteId. - Usar um array constante (
FASES) eindexOfpara saber em que fase está um negócio e passá-lo à seguinte. - Desenhar um quadro com um ciclo dentro de outro ciclo (uma coluna por fase, um cartão por negócio).
🧰 Antes de começar
- Abre a pasta
meu-crmno VS Code e oindex.htmlcom o Live Server. - Confirma que a ficha do cliente da aula 13 funciona (o 👁️ abre a ficha e consegues registar uma interação).
- Tem pelo menos 3 clientes criados: vais precisar deles para as oportunidades.
😕 Faltaste à aula 13 ou o teu projeto não funciona? Descarrega o ponto de controlo da aula 13, descompacta-o e trabalha a partir dele.
Parte A — O negócio: o funil de vendas (≈ 15 min)
💡 Do contacto à venda
Um cliente não compra logo no primeiro contacto. Primeiro mostra interesse, depois pede um orçamento, discute o preço… e só no fim diz «sim» (ou «não»). A cada possível negócio com um cliente chamamos uma oportunidade: tem um título (ex.: «Contrato anual de manutenção»), um valor estimado e uma data prevista para fechar.
As empresas organizam as oportunidades por fases. No nosso CRM são cinco:
| Fase | O que quer dizer | Exemplo (oficina de automóveis) |
|---|---|---|
| Novo | Identificámos um possível negócio. | Uma empresa de entregas perguntou pela revisão das suas 5 carrinhas. |
| Proposta | Enviámos um orçamento. | Enviámos orçamento: 1500 €. |
| Negociação | O cliente está a discutir preço ou condições. | Pedem desconto se for também a mudança de pneus. |
| Ganho | O cliente aceitou. É uma venda! 🎉 | Contrato assinado. |
| Perdido | O cliente recusou ou desistiu. | Escolheram outra oficina. |
A isto chama-se funil de vendas (em inglês, pipeline): como num funil, entram muitos contactos em cima e só alguns chegam lá abaixo, a «Ganho». O funil ajuda o gestor a responder a perguntas como «quantos negócios temos em negociação?» ou «quanto dinheiro pode entrar este mês?».
Para ver o funil, os CRM usam um quadro kanban: uma coluna por fase e um cartão por negócio, como post-its num quadro. Quando o negócio avança, o cartão passa para a coluna seguinte, da esquerda para a direita. É o que fazem programas como o Trello, o Pipedrive ou o HubSpot — e é o que vais construir hoje.
🔗 Repara: é outra vez uma relação 1 para N, como na aula 13. Um cliente pode ter muitas oportunidades, e cada oportunidade guarda o clienteId (chave estrangeira) do seu cliente.
🤔 Pensa: se tiveres 10 oportunidades em «Novo», 4 em «Proposta» e 1 em «Ganho», o que é que isso te diz sobre o teu negócio?
Parte B — O quadro de oportunidades (≈ 45 min)
1 Um link novo no menu
No index.html, dentro do <nav class="menu">, está o comentário AULA 14: COLAR AQUI O LINK DAS OPORTUNIDADES. Cola lá o link.
O href="#oportunidades" quer dizer «salta para o elemento com id="oportunidades" nesta página» — é uma âncora.
✅ Deves ver: o link Oportunidades no menu do topo. Por agora, clicar nele não faz nada (a secção ainda não existe).
2 A secção das oportunidades (HTML)
⚠️ Atenção ao sítio: no index.html há dois comentários da aula 14. O do passo 1 está no menu; este bloco vai para o outro, dentro do <main>: AULA 14: COLAR AQUI AS OPORTUNIDADES (por baixo da ficha do cliente da aula 13).
Repara no formulário:
- O
<select id="op-cliente">está vazio: as opções (os teus clientes) vão ser criadas pelo JavaScript. - O
<input type="number" min="0" step="0.01">só aceita números, a partir de 0, com cêntimos. - O
<div id="quadro">também está vazio: é lá que o JavaScript vai desenhar as colunas e os cartões.
✅ Deves ver: um cartão novo, Oportunidades de negócio, com o formulário. A lista de clientes está vazia e ainda não há quadro — é normal. O link do menu já te leva até lá.
3 As funções das oportunidades (JavaScript)
⚠️ Atenção ao sítio: como na aula passada, há dois comentários da aula 14 no app.js: // ===== AULA 14: COLAR AQUI ===== e // AULA 14: COLAR AQUI (dentro desta função). Este bloco vai para o grande, com =====, perto do fim do ficheiro (por baixo das funções da aula 13). Não o coles dentro de atualizarEcra().
O que há de novo neste bloco:
const FASES = ['Novo', 'Proposta', 'Negociação', 'Ganho', 'Perdido'];— a lista das fases, pela ordem. É umaconstporque nunca muda; o nome em maiúsculas é um costume dos programadores para dizer «isto é um valor fixo».let oportunidades = carregar('oportunidades');— a terceira tabela do CRM (chavecrm_oportunidadesno browser).preencherSelectClientes()— cria uma<option>por cliente: o que se vê é o nome, mas o valor escondido (value) é o id.guardarOportunidade()— valida, cria o objeto e grava. Tem duas vezesNumber(…): o valor de um<select>ou de um<input>é sempre texto, mesmo que pareça um número.Number('1500')transforma o texto'1500'no número1500. A fase começa sempre em'Novo'.procurarOportunidade(id)— igual à ideia doprocurarClienteda aula 9.desenharQuadro()— um ciclo dentro de outro ciclo: para cada fase, abre uma coluna e, lá dentro, percorre todas as oportunidades e põe só as que estão nessa fase.cartaoOportunidade(op)— cria o HTML de um cartão. Para mostrar o nome do cliente, usa a chave estrangeira:procurarCliente(op.clienteId). É como umJOINda aula 11!avancarFase(id)—FASES.indexOf(op.fase)diz a posição da fase atual na lista (Novo = 0, Proposta = 1…). A fase seguinte éFASES[posicao + 1].apagarOportunidade(id)— igual aoapagarInteracaoda aula 13.
✅ Deves ver: ainda nada de novo no ecrã e nenhum erro na Consola. Escreve na Consola FASES e oportunidades para confirmares que existem.
4 Pôr o quadro a funcionar
Falta chamar as funções novas sempre que os dados mudam. Cola estas duas linhas dentro de atualizarEcra(), por baixo do comentário pequeno // AULA 14: COLAR AQUI.
🤔 Prevê: porque é que preencherSelectClientes() tem de estar em atualizarEcra()? O que aconteceria à lista de clientes do formulário quando criasses um cliente novo, se esta linha não estivesse lá?
Confere a tua função:
✅ Deves ver: a lista Cliente do formulário tem os teus clientes e, por baixo, aparecem 5 colunas: NOVO, PROPOSTA, NEGOCIAÇÃO, GANHO e PERDIDO (o CSS põe os títulos em maiúsculas).
5 Testar
- Cria 3 oportunidades para clientes diferentes, com valores diferentes. Inventa negócios do teu setor, por exemplo: «Contrato anual» — 1500, «Encomenda de 50 unidades» — 800, «Projeto de remodelação» — 3200.
- Tenta criar uma oportunidade sem escolher cliente: deve aparecer o aviso.
- Clica no ▶ de uma oportunidade várias vezes: o cartão passa de coluna em coluna até Ganho… e lá fica.
- Carrega em F5: as oportunidades continuam nas mesmas colunas.
- Cria um cliente novo na secção Clientes e confirma que ele aparece logo na lista do formulário das oportunidades.
- Apaga uma oportunidade com o 🗑️.
✅ Deves ver: cada cartão com o título, o nome do cliente e o valor (ex.: 1500.00 €). Repara que o valor aparece com ponto: em Portugal escrevemos com vírgula — vamos resolver isso na aula 15.
😕 Não funciona?
- A lista de clientes do formulário está vazia → falta
preencherSelectClientes();no passo 4, ou não tens clientes criados. Cannot read properties of null (reading 'value')→ falta a secção do passo 2 (o JavaScript não encontra oop-cliente), ou colaste-a no sítio errado.- O formulário das oportunidades aparece dentro do menu do topo → colaste o bloco do passo 2 no comentário do menu. Corta e cola no comentário dentro do
<main>. preencherSelectClientes is not defined→ o bloco do passo 3 não está no sítio certo (talvez dentro deatualizarEcra()).Identifier 'FASES' has already been declared→ colaste o bloco do passo 3 duas vezes.- O cartão diz «(cliente apagado)», mas o cliente existe → tiraste o
Number(…)à volta doclienteId: o id ficou guardado como texto e'1759…' === 1759…éfalse. Volta a copiar o bloco, apaga a oportunidade errada e cria-a de novo. op.valor.toFixed is not a function→ o valor ficou guardado como texto (falta oNumber(…)). Corrige como acima.
🔎 Investiga
- Porque é que o ▶ pára em «Ganho»? Lê a condição
if (posicao < FASES.indexOf('Ganho')). Que número dáFASES.indexOf('Ganho')? E se uma oportunidade estiver em «Perdido», o ▶ faz alguma coisa? - Cria um cliente «Teste», cria-lhe uma oportunidade e depois apaga o cliente. O que aparece no cartão? Porquê? (Dica: o que devolve
procurarClientequando não encontra o cliente?) - Na Consola, escreve
FASES.indexOf('Proposta'), depoisFASES[2]e depoisFASES.indexOf('Ganhou'). O que quer dizer o-1? - No DevTools (Aplicação → Armazenamento local), abre
crm_oportunidades. Aparece o nome do cliente? O que aparece no lugar dele?
✍️ Exercícios (fazem parte do teu projeto)
Estes exercícios não têm o código pronto: inspira-te na função avancarFase.
E1 · Botão ◀ «recuar fase»
Acrescenta a cada cartão um botão ◀ (antes do ▶) que passa a oportunidade para a fase anterior. Em «Novo» não faz nada. Caso especial: uma oportunidade em «Perdido», ao recuar, deve voltar a «Negociação» (o negócio é reaberto) — e não passar a «Ganho»!
💡 Dica: cria uma função recuarFase(id) parecida com avancarFase. Se avançar é posicao + 1, recuar é…? E a condição para não sair da lista é posicao > 0. O caso «Perdido» resolve-se com um if antes de tudo. O botão vai para cartaoOportunidade(), dentro do <div class="acoes">, igual ao do ▶ mas a chamar a tua função.
E2 · Botão ✖ «perdido»
Acrescenta a cada cartão um botão ✖ (com o title="Marcar como perdido") que passa a oportunidade diretamente para a fase «Perdido», esteja ela em que fase estiver.
💡 Dica: uma função marcarPerdido(id) com 4 linhas: procurar a oportunidade, mudar a fase, gravar, atualizar o ecrã. Vê como o avancarFase faz cada uma destas coisas.
⭐ Desafio · Total em euros em cada coluna
No topo de cada coluna, junto ao nome da fase, mostra o valor total das oportunidades dessa fase, por exemplo «NEGOCIAÇÃO · 4700.00 €».
💡 Dica: em desenharQuadro(), antes da linha que abre a coluna (html += `<div class="coluna">…), soma os valores das oportunidades dessa fase com um acumulador (como o total da semana da aula 7). Depois mostra o total no <h3> com toFixed(2).
📦 O que mostrar ao professor
⏱️ Se sobrar tempo
- Faz o desafio ⭐.
- Mostra a data prevista em cada cartão (o campo
op.dataPrevistajá está guardado). - Mostra quantos cartões tem cada coluna, por exemplo «PROPOSTA (2)».
- Pensa no teu setor: que nome dariam às fases? (Numa oficina: Pedido, Orçamento, Aprovado, Concluído…) Aponta as ideias para a aula 17.
🤖 Pedir ajuda à IA (opcional)
Queres perceber melhor como as empresas usam o funil de vendas? Pede a uma IA gratuita que te explique. Copia este pedido, troca o texto entre [ ] e cola-o na IA:
📖 Palavras novas
- Oportunidade
- Um possível negócio com um cliente, com título, valor estimado, fase e data prevista.
- Funil de vendas (pipeline)
- As fases por que passa um negócio, do primeiro contacto até ser ganho ou perdido. Entram muitos, chegam ao fim poucos.
- Quadro kanban
- Quadro com uma coluna por fase e um cartão por tarefa ou negócio; os cartões andam da esquerda para a direita.
- Âncora (
href="#id") - Link que salta para o elemento com aquele
idna mesma página. - Constante (
const FASES) - Variável cujo valor não muda. Nomes em maiúsculas indicam valores fixos.
indexOf- Devolve a posição de um elemento num array (começa no 0), ou
-1se não existir. Number()- Transforma texto em número:
Number('1500')dá1500. - Ciclo dentro de ciclo
- Um ciclo que, a cada volta, corre outro ciclo inteiro (aqui: para cada fase, percorrer todas as oportunidades).