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

🧰 Antes de começar

  1. Abre a pasta meu-crm no VS Code e o index.html com o Live Server.
  2. Confirma que a ficha do cliente da aula 13 funciona (o 👁️ abre a ficha e consegues registar uma interação).
  3. 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:

FaseO que quer dizerExemplo (oficina de automóveis)
NovoIdentificámos um possível negócio.Uma empresa de entregas perguntou pela revisão das suas 5 carrinhas.
PropostaEnviámos um orçamento.Enviámos orçamento: 1500 €.
NegociaçãoO cliente está a discutir preço ou condições.Pedem desconto se for também a mudança de pneus.
GanhoO cliente aceitou. É uma venda! 🎉Contrato assinado.
PerdidoO 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:

✅ 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:

✅ 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

  1. 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.
  2. Tenta criar uma oportunidade sem escolher cliente: deve aparecer o aviso.
  3. Clica no ▶ de uma oportunidade várias vezes: o cartão passa de coluna em coluna até Ganho… e lá fica.
  4. Carrega em F5: as oportunidades continuam nas mesmas colunas.
  5. Cria um cliente novo na secção Clientes e confirma que ele aparece logo na lista do formulário das oportunidades.
  6. 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 o op-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 de atualizarEcra()).
  • 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 do clienteId: 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 o Number(…)). Corrige como acima.

🔎 Investiga

  1. 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?
  2. Cria um cliente «Teste», cria-lhe uma oportunidade e depois apaga o cliente. O que aparece no cartão? Porquê? (Dica: o que devolve procurarCliente quando não encontra o cliente?)
  3. Na Consola, escreve FASES.indexOf('Proposta'), depois FASES[2] e depois FASES.indexOf('Ganhou'). O que quer dizer o -1?
  4. 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

🤖 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 id na 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 -1 se 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).