Files
COMERCIAL-GTI/public/index.html
T
Gustavo d330bb5ff0 Baseline commit of the existing Maquina Comercial Pessoal project
Tracks the app that was already in place (capture, treatment, outreach,
WhatsApp integration, server, frontend, tests, docs) so future feature
work can branch and use isolated worktrees.
2026-07-31 10:54:07 -03:00

558 lines
24 KiB
HTML

<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Máquina Comercial Pessoal</title>
<link rel="stylesheet" href="/css/app.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22><rect width=%2264%22 height=%2264%22 rx=%2216%22 fill=%22%23f06a25%22/><text x=%2232%22 y=%2241%22 text-anchor=%22middle%22 font-family=%22Arial%22 font-size=%2226%22 font-weight=%22700%22 fill=%22white%22>MC</text></svg>">
</head>
<body>
<header class="topbar">
<div class="brand">
<span class="brand-mark brand-monogram" aria-hidden="true">MC</span>
<span class="brand-name">Máquina Comercial Pessoal</span>
</div>
<nav class="tabs" role="tablist">
<button class="tab active" data-tab="captura" role="tab" aria-selected="true">📡 Capturar Leads</button>
<button class="tab" data-tab="tratamento" role="tab" aria-selected="false">📊 Tratar Leads</button>
<button class="tab" data-tab="campanhas" role="tab" aria-selected="false">📢 Campanhas</button>
</nav>
<span class="muted" id="usuario-logado"></span>
<button class="btn-ghost hidden" id="btn-usuarios" title="Usuários">👤 Usuários</button>
<button class="btn-ghost" id="btn-settings" title="Configurações">⚙️</button>
<button class="btn-ghost" id="btn-sair" title="Sair">🚪 Sair</button>
</header>
<main>
<!-- ==================== CAPTURA ==================== -->
<section id="tab-captura" class="tab-panel active">
<div class="captura-wrap">
<div class="card" id="captura-form">
<div class="row space-between">
<h2>Nova captura</h2>
<button class="btn-ghost" id="btn-ver-historico" type="button">🕓 Histórico de capturas</button>
</div>
<div class="field">
<label for="sel-uf">1. Estado (UF)</label>
<div class="row">
<select id="sel-uf"></select>
<span id="uf-status" class="chip"></span>
</div>
<div id="extracao-aviso" class="notice hidden">
<p>A base CNPJ de <strong id="extracao-uf"></strong> ainda não foi extraída neste computador.
O primeiro uso baixa cerca de <strong>6 GB</strong> da Receita Federal e depois extrai a UF. O tempo depende da internet e pode passar de uma hora; downloads interrompidos são retomados.</p>
<button class="btn" id="btn-extrair">Baixar e extrair base</button>
</div>
</div>
<div class="field">
<label>2. Cidades</label>
<div class="row">
<label class="radio"><input type="radio" name="cidades-modo" value="todas" checked> Todas as cidades da UF</label>
<label class="radio"><input type="radio" name="cidades-modo" value="escolher"> Escolher cidades</label>
</div>
<div id="cidades-picker" class="picker hidden">
<input type="search" id="busca-cidade" placeholder="Digite para buscar cidade...">
<div id="cidades-sugestoes" class="sugestoes"></div>
<div id="cidades-chips" class="chips"></div>
</div>
</div>
<div class="field">
<label>3. Nichos <span class="muted" id="nichos-contagem"></span></label>
<div class="row">
<label class="radio"><input type="radio" name="nichos-modo" value="todos" checked> Todos os nichos</label>
<label class="radio"><input type="radio" name="nichos-modo" value="escolher"> Escolher nichos</label>
</div>
<div id="nichos-picker" class="picker hidden">
<div class="row">
<input type="search" id="busca-nicho" placeholder="Filtrar nichos...">
<button class="btn-ghost" id="btn-nichos-todos" type="button">Marcar visíveis</button>
<button class="btn-ghost" id="btn-nichos-nenhum" type="button">Desmarcar todos</button>
</div>
<div id="nichos-grid" class="nichos-grid"></div>
</div>
<details class="cnae-avulso">
<summary>Não achou o nicho? Buscar qualquer atividade da Receita Federal (CNAE)</summary>
<p class="muted cnae-avulso-hint">Os 57 nichos acima já vêm com mensagem e interesse configurados. Um CNAE avulso é adicionado só para a busca — ele participa da captura normalmente.</p>
<input type="search" id="busca-cnae" placeholder="Ex.: salão de beleza, transportadora, escola...">
<div id="cnae-sugestoes" class="sugestoes"></div>
<div id="cnae-chips" class="chips"></div>
</details>
</div>
<div class="field">
<label>4. Fontes de busca</label>
<div id="fontes-lista" class="fontes"></div>
</div>
<details class="field">
<summary>Opções avançadas</summary>
<div class="row">
<label>Máx. por consulta (fontes de API)
<input type="number" id="inp-max" value="10" min="1" max="60">
</label>
<label>Limite total de consultas (0 = sem limite)
<input type="number" id="inp-query-limit" value="0" min="0">
</label>
</div>
</details>
<button class="btn btn-primary btn-lg" id="btn-capturar">🚀 Iniciar captura</button>
</div>
<div class="card hidden" id="progresso-card">
<div class="row space-between">
<h2 id="progresso-titulo">Captura em andamento</h2>
<button class="btn-danger" id="btn-cancelar">Cancelar</button>
</div>
<div class="progress-bar"><div class="progress-fill" id="progresso-fill"></div></div>
<p class="progress-msg" id="progresso-msg">iniciando...</p>
<div class="stat-row" id="progresso-stats"></div>
<div class="log" id="progresso-log"></div>
</div>
</div>
</section>
<!-- ==================== TRATAMENTO ==================== -->
<section id="tab-tratamento" class="tab-panel">
<div class="card filtros-card">
<div class="filtros" id="filtros">
<select id="f-uf" data-filtro="uf"><option value="">UF: todas</option></select>
<input list="dl-cidades" id="f-cidade" data-filtro="cidade" placeholder="Cidade: todas">
<datalist id="dl-cidades"></datalist>
<select id="f-segmento" data-filtro="segmento"><option value="">Nicho: todos</option></select>
<select id="f-classificacao" data-filtro="classificacao">
<option value="">Classificação: todas</option>
<option value="excelente">🟢 Excelente</option>
<option value="bom">🟢 Bom</option>
<option value="medio">🟡 Médio</option>
<option value="baixo">🟠 Baixo</option>
<option value="muito_baixo">🔴 Muito Baixo</option>
</select>
<select id="f-prioridade" data-filtro="prioridade">
<option value="">Prioridade: todas</option>
<option value="1">P1 — atacar agora</option>
<option value="2">P2 — esta semana</option>
<option value="3">P3 — quando sobrar</option>
<option value="4">P4 — campanhas específicas</option>
</select>
<select id="f-produto" data-filtro="produto"><option value="">Produto: todos</option></select>
<select id="f-interesse" data-filtro="interesse"><option value="">Interesse: todos</option></select>
<select id="f-telefone" data-filtro="telefone_valido">
<option value="">Telefone: todos</option>
<option value="sim">Com telefone</option>
<option value="nao">Sem telefone</option>
</select>
<select id="f-website" data-filtro="website">
<option value="">Website: todos</option>
<option value="sim">Com website</option>
<option value="nao">Sem website</option>
</select>
<select id="f-email" data-filtro="email">
<option value="">E-mail: todos</option>
<option value="sim">Com e-mail</option>
<option value="nao">Sem e-mail</option>
</select>
<select id="f-exportado" data-filtro="exportado">
<option value="">Exportação: todos</option>
<option value="nao">Ainda não exportados</option>
<option value="sim">Já exportados</option>
</select>
<input type="search" id="f-busca" data-filtro="q" placeholder="🔎 empresa, CNPJ ou telefone">
<button class="btn-ghost" id="btn-limpar-filtros">Limpar</button>
</div>
</div>
<div class="stat-tiles" id="stat-tiles"></div>
<div class="grid-3">
<div class="card">
<div class="row space-between">
<h2>Top cidades</h2>
</div>
<div id="rank-cidades" class="ranking"></div>
</div>
<div class="card">
<div class="row space-between">
<h2>Top nichos</h2>
</div>
<div id="rank-segmentos" class="ranking"></div>
</div>
<div class="card">
<div class="row space-between">
<h2 id="rank-produtos-titulo">Top produto recomendado</h2>
</div>
<div id="rank-produtos" class="ranking"></div>
</div>
</div>
<div class="grid-3">
<div class="card" id="card-oportunidades-dia"></div>
<div class="card" id="card-maior-oportunidade"></div>
<div class="card" id="card-evolucao-diaria"></div>
</div>
<div class="card" id="card-funil"></div>
<div class="card">
<div class="row space-between wrap">
<h2>Leads <span class="muted" id="leads-total"></span></h2>
<button class="btn btn-primary" id="btn-exportar">📤 Gerar relatório / exportação com este filtro</button>
</div>
<div class="table-wrap">
<table id="tabela-leads">
<thead>
<tr>
<th class="acao-col">Ação</th><th>Empresa</th><th>Telefone</th><th>Status WhatsApp</th><th>Cidade</th><th>Nicho</th>
<th>Prioridade</th><th class="num">Geral</th>
<th>Classificação</th><th>Produto</th><th>Exportado</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="row space-between">
<button class="btn-ghost" id="pg-prev">← Anterior</button>
<span class="muted" id="pg-info"></span>
<button class="btn-ghost" id="pg-next">Próxima →</button>
</div>
</div>
<div class="card">
<h2>Exportações geradas</h2>
<div id="historico-exports" class="lista"></div>
</div>
</section>
<!-- ==================== CAMPANHAS ==================== -->
<section id="tab-campanhas" class="tab-panel">
<div class="card">
<div class="row space-between">
<h2>Campanhas</h2>
</div>
<div id="campaign-cards" class="campaign-cards"></div>
</div>
<div class="card" id="card-queue-preview">
<div class="row space-between">
<h2>Fila do dia</h2>
</div>
<div id="campaign-queue">
<p class="muted">Selecione uma campanha e clique em "Preparar fila" para gerar itens.</p>
</div>
</div>
<div class="card">
<div class="row space-between">
<h2>Respostas pendentes</h2>
</div>
<div id="pending-replies" class="lista">
<p class="muted">Nenhuma resposta pendente.</p>
</div>
</div>
<div class="card">
<div class="row space-between">
<h2>Classificação de estágio</h2>
</div>
<div id="stage-classification">
<p class="muted">Classifique leads que responderam para acompanhar o funil comercial.</p>
</div>
</div>
<div class="card">
<div class="row space-between">
<h2>Agendar demonstração / teste</h2>
<button class="btn btn-primary btn-sm" id="btn-schedule-appointment" type="button">Agendar</button>
</div>
<div id="appointment-form">
<p class="muted">Após classificar um lead, use o botão "Classificar" para agendar demonstração ou teste.</p>
</div>
</div>
<div class="card">
<div class="row space-between">
<h2>Métricas</h2>
</div>
<div id="metrics-filters" class="row"></div>
<div id="campaign-metrics" style="margin-top:10px">
<p class="muted">Selecione uma campanha e prepare a fila para ver métricas.</p>
</div>
</div>
</section>
</main>
<!-- Modal de exportação -->
<dialog id="modal-export">
<form method="dialog" class="modal-body" id="form-export">
<h2>Gerar relatório / exportação</h2>
<p class="muted" id="export-recap"></p>
<div id="export-atalhos" class="chips"></div>
<div class="field">
<label>Tipo de arquivo</label>
<label class="radio"><input type="radio" name="export-tipo" value="excel" checked>
<strong>CSV + XLS</strong> — relatório completo com os filtros atuais</label>
</div>
<label class="radio"><input type="checkbox" id="export-marcar" checked>
Marcar estes leads como exportados (não aparecem de novo no filtro "ainda não exportados")</label>
<div class="row space-between">
<button class="btn-ghost" value="cancel">Cancelar</button>
<button class="btn btn-primary" id="btn-confirmar-export" value="default">Gerar arquivos</button>
</div>
</form>
</dialog>
<!-- Modal de histórico de capturas -->
<dialog id="modal-historico">
<div class="modal-body">
<div class="row space-between">
<h2>Últimas capturas</h2>
<button class="btn-ghost" id="btn-fechar-historico" type="button">Fechar</button>
</div>
<div id="historico-capturas" class="lista"></div>
</div>
</dialog>
<!-- Modal de configurações -->
<dialog id="modal-settings">
<form method="dialog" class="modal-body">
<div class="settings-header">
<div>
<h2>Configurações</h2>
<p class="muted">Conexão local, mensagens, limites e lista de bloqueio.</p>
</div>
<span class="chip" id="outreach-status"></span>
</div>
<section class="settings-card whatsapp-connection-card">
<div class="connection-copy">
<div class="row">
<label class="settings-title">Conexão WhatsApp</label>
<span class="chip connection-state" id="whatsapp-state" role="status" aria-live="polite">Consultando...</span>
</div>
<p class="muted">Conexão local via dispositivo vinculado. A automação inicia desligada.</p>
<p id="whatsapp-number" class="connection-number hidden"></p>
<p id="whatsapp-error" class="erro hidden"></p>
<div class="connection-actions admin-only">
<button class="btn btn-primary" id="btn-whatsapp-connect" type="button">Conectar / gerar QR</button>
<button class="btn-ghost" id="btn-whatsapp-refresh-qr" type="button">Atualizar QR</button>
<button class="btn-ghost" id="btn-whatsapp-disconnect" type="button">Desconectar</button>
<button class="btn-danger" id="btn-whatsapp-logout" type="button">Sair e esquecer sessão</button>
</div>
</div>
<div class="qr-panel">
<img id="whatsapp-qr" class="hidden" alt="QR code para vincular o WhatsApp">
<span id="whatsapp-qr-placeholder" class="muted">O QR aparecerá aqui quando solicitado.</span>
<span id="whatsapp-qr-expiry" class="muted"></span>
</div>
</section>
<section class="settings-card settings-principal admin-only">
<div>
<label class="settings-title">Envio WhatsApp</label>
<p class="muted">O limite vale para envios manuais e automáticos feitos hoje.</p>
</div>
<div class="settings-controls">
<label class="toggle"><input type="checkbox" id="outreach-ativo"> Automação ativa</label>
<label>Limite por dia <input type="number" id="outreach-limite" min="1"></label>
</div>
</section>
<details class="settings-section" open>
<summary>Enviar mensagem manual</summary>
<p class="muted">Revise o número, a mensagem e o contexto real do lead antes de enviar.</p>
<div class="manual-send-card">
<label class="full">Número do WhatsApp
<input type="text" id="manual-numero" placeholder="5511999999999" inputmode="numeric">
</label>
<label class="full">Mensagem
<textarea id="manual-mensagem" rows="3" placeholder="Digite uma mensagem pessoal e respeitosa..."></textarea>
</label>
<div class="manual-lead-context full" id="manual-lead-context">Envio avulso, sem lead selecionado.</div>
<button class="btn-ghost btn-sm" id="btn-limpar-lead-manual" type="button">Limpar contexto do lead</button>
<div class="manual-actions full">
<span class="muted" id="manual-status"></span>
<button class="btn btn-primary" id="btn-enviar-manual" type="button">Enviar mensagem</button>
</div>
<button class="btn-ghost btn-sm" id="btn-toggle-outreach-log" type="button">Ver log de envios</button>
<div id="outreach-ultimos" class="lista hidden full"></div>
</div>
</details>
<details class="settings-section">
<summary>Alterar minha senha</summary>
<p class="muted">Disponível para qualquer usuário autenticado. A nova senha deve ter ao menos 8 caracteres.</p>
<div class="opt-out-form">
<label>Senha atual <input type="password" id="senha-atual" autocomplete="current-password"></label>
<label>Nova senha <input type="password" id="senha-nova" minlength="8" autocomplete="new-password"></label>
<label>Confirmar nova senha <input type="password" id="senha-confirmacao" minlength="8" autocomplete="new-password"></label>
<button class="btn btn-primary" id="btn-alterar-senha" type="button">Alterar senha</button>
</div>
<p id="senha-status" class="muted"></p>
</details>
<details class="settings-section admin-only">
<summary>Templates automáticos</summary>
<p class="muted">Somente mensagem e estado ativo. Os templates aparecem após configurar seus serviços.</p>
<div id="outreach-templates" class="lista"></div>
<div class="row space-between">
<span></span>
<button class="btn-ghost" id="btn-salvar-outreach" type="button">Salvar templates</button>
</div>
</details>
<details class="settings-section admin-only">
<summary>Lista de bloqueio / opt-out</summary>
<p class="muted">Números nesta lista nunca recebem envios manuais ou automáticos.</p>
<div class="opt-out-form">
<label>Número <input type="text" id="optout-numero" placeholder="5511999999999" inputmode="numeric"></label>
<label>Motivo <input type="text" id="optout-motivo" placeholder="Pedido do contato"></label>
<button class="btn btn-primary" id="btn-optout-add" type="button">Adicionar</button>
</div>
<p id="optout-status" class="muted"></p>
<div id="optout-list" class="lista"></div>
</details>
<details class="settings-section zona-risco admin-only">
<summary>Zona de risco</summary>
<p class="muted">Apaga permanentemente todos os leads, exportações, capturas e extrações. Configurações e nichos não são afetados.</p>
<button class="btn-danger" id="btn-limpar-base" type="button">🗑️ Limpar toda a base de leads</button>
</details>
<div class="row space-between">
<button class="btn-ghost" value="cancel">Fechar</button>
<button class="btn btn-primary admin-only" id="btn-salvar-settings" value="default">Salvar</button>
</div>
</form>
</dialog>
<!-- Modal de confirmacao da limpeza total da base -->
<dialog id="modal-reset-base">
<div class="modal-body">
<h2>Limpar toda a base de leads</h2>
<p class="muted">Esta ação é <strong>irreversível</strong>: todos os leads, exportações, capturas e extrações serão apagados. Digite a senha de segurança para confirmar.</p>
<div class="field">
<label>Senha de segurança <input type="password" id="reset-base-senha" autocomplete="off"></label>
</div>
<p class="muted erro hidden" id="reset-base-erro"></p>
<div class="row space-between">
<button class="btn-ghost" id="btn-cancelar-reset-base" type="button">Cancelar</button>
<button class="btn-danger" id="btn-confirmar-reset-base" type="button">Confirmar limpeza</button>
</div>
</div>
</dialog>
<!-- Modal de usuarios (somente admin) -->
<dialog id="modal-usuarios">
<div class="modal-body">
<div class="row space-between">
<h2>Usuários</h2>
<button class="btn-ghost" id="btn-fechar-usuarios" type="button">Fechar</button>
</div>
<div id="lista-usuarios" class="lista"></div>
<form class="field" id="form-novo-usuario">
<label>Novo usuário</label>
<div class="row">
<input type="text" id="novo-usuario-username" placeholder="usuário" minlength="3" required>
<input type="password" id="novo-usuario-password" placeholder="senha" minlength="8" required>
<select id="novo-usuario-role">
<option value="comercial">Comercial</option>
<option value="admin">Administrador</option>
</select>
<button class="btn btn-primary" type="submit">Criar</button>
</div>
</form>
</div>
</dialog>
<!-- Modal detalhe do lead -->
<dialog id="modal-lead">
<div class="modal-body" id="lead-detalhe"></div>
</dialog>
<!-- Modal inicio de campanha -->
<dialog id="modal-campaign-start">
<div class="modal-body">
<h2>Iniciar campanha</h2>
<p class="muted">Confirma o início da <strong class="campaign-start-name">campanha</strong>?</p>
<p class="muted">A automação começará a enviar mensagens dentro da janela configurada.</p>
<p class="muted erro hidden" id="modal-campaign-start-erro"></p>
<div class="row space-between">
<button class="btn-ghost" onclick="this.closest('dialog').close()">Cancelar</button>
<button class="btn btn-primary btn-confirm-start" id="btn-confirm-start">Iniciar</button>
</div>
</div>
</dialog>
<!-- Modal classificacao de estagio -->
<dialog id="modal-campaign-stage">
<div class="modal-body">
<h2>Classificar lead</h2>
<div class="field">
<label for="inp-stage">Estágio</label>
<select id="inp-stage"></select>
</div>
<p class="muted erro hidden" id="modal-campaign-stage-erro"></p>
<div class="row space-between">
<button class="btn-ghost" onclick="this.closest('dialog').close()">Cancelar</button>
<button class="btn btn-primary btn-save-stage" id="btn-save-stage">Salvar</button>
</div>
</div>
</dialog>
<!-- Modal agendamento -->
<dialog id="modal-campaign-appointment">
<div class="modal-body">
<h2>Agendar demonstração / teste</h2>
<div class="field">
<label for="inp-appointment-lead-id">ID do lead</label>
<input type="number" id="inp-appointment-lead-id" min="1">
</div>
<div class="field">
<label for="inp-appointment-kind">Tipo</label>
<select id="inp-appointment-kind">
<option value="demonstracao">Demonstração</option>
<option value="teste_g_delivery">Teste G-Delivery</option>
</select>
</div>
<div class="field">
<label for="inp-appointment-participant">Participante</label>
<input type="text" id="inp-appointment-participant" placeholder="Nome do contato">
</div>
<div class="field">
<label for="inp-appointment-company">Empresa</label>
<input type="text" id="inp-appointment-company" placeholder="Nome da empresa">
</div>
<div class="row">
<div class="field">
<label for="inp-appointment-day">Dia preferido</label>
<input type="date" id="inp-appointment-day">
</div>
<div class="field">
<label for="inp-appointment-time">Horário preferido</label>
<input type="time" id="inp-appointment-time">
</div>
</div>
<p class="muted erro hidden" id="modal-campaign-appointment-erro"></p>
<div class="row space-between">
<button class="btn-ghost" onclick="this.closest('dialog').close()">Cancelar</button>
<button class="btn btn-primary btn-confirm-appointment" id="btn-confirm-appointment">Agendar</button>
</div>
</div>
</dialog>
<div id="toast" class="toast hidden"></div>
<script src="/js/api.js"></script>
<script src="/js/captura.js"></script>
<script src="/js/tratamento.js"></script>
<script src="/js/campanhas.js"></script>
<script src="/js/app.js"></script>
</body>
</html>