Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
Jira Insights
Translation pending
BusinessJavaScript · CSSGitHub @dfg-dexterityOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Dexterity Hub (antes "Insights de Uso") · Jira + Clockwork + Odoo Painel interno da Dexterity IT que nasceu para agilizar o apontamento de horas do time (Jira + Clockwork) e virou o workspace de gestão da empresa.Read the full README ↗
Painel interno da Dexterity IT que nasceu para agilizar o apontamento de horas do time
(Jira + Clockwork) e virou o workspace de gestão da empresa. Desde 2026-09-13 a navegação é
por perfil: o Dexterity Hub é o conteúdo genérico, para todos (Início, ⏱ Apontar, 📥 Inbox,
🎯 Prioridades, 👤 Meu trabalho, ➕ Criar ticket — e 📆 Hoje); as áreas Dexterity Entrega (gestores de entrega/PMs),
Dexterity Negócio (comercial, financeiro, controladoria) e Dexterity Insights (diretoria e
governança) aparecem pela lente do perfil de cada pessoa (cfg.papeis, editado na Central de
configurações) — lente, nunca permissão: toda tela continua abrindo por link e pelo Ctrl+K. O
catálogo de telas (NAVCAT em public/js/26-navegacao.js) é a fonte de verdade da barra, da paleta
e do gate npm run check; slugs aposentados viram alias (VISTA_ALIAS) para os links antigos
não quebrarem.
Front estático (public/, sem build) + funções serverless no Vercel. O Vercel
serve public/ na raiz e cada arquivo em api/*.js vira um endpoint.
Leitura (conta de serviço, via variáveis de ambiente — nunca no navegador):
GET /api/tempo — apontamentos do Clockwork na janela; resolve projeto/tipo no Jira e classifica faturável.GET /api/atividade — issues atualizadas no Jira com changelog → eventos por autor da alteração.GET /api/vencimentos — issues com vencimento/sem responsável (central de alertas).GET /api/projetos, GET /api/usuarios — metadados (projetos/pessoas) para filtros e formulários.GET|POST /api/config — config compartilhada do time (metas/ausências/contratos), o painel do cliente (?portal=<token>) e a 🔐 área do cliente com login (?pcli=<ação>). A gravação exige token do Jira (cabeçalhos X-Jira-Email/X-Jira-Token).POST /api/resumo — resumo executivo por IA (Anthropic).POST /api/teams — relatório diário de apontamento no Teams (cron via GitHub Actions).Escrita com o token da própria pessoa (enviado por requisição, nunca persistido):
POST /api/apontar — apontamento (Clockwork) + convites de reunião.POST /api/transicao — transição de status, reagendar, atribuir, comentar.POST /api/criar — criação de issues (planejamento em lote).POST /api/reunioes — mover/reclassificar issues entre projetos/tipos; agenda do Outlook; 🔒 trava do ticket de
reunião ({agtrava:'ler'|'reservar'|'confirmar'|'liberar'}) e ⏰ alertas do organizador ({agalertas:'ler'|'dispensar'}).Os tokens de serviço ficam só nas variáveis de ambiente do servidor — nunca no navegador (a chave do Supabase também é usada apenas no servidor). Front e funções no mesmo domínio: sem CORS.
api/
_lib/util.js datas (America/Sao_Paulo), cache, fetch do Jira/Clockwork, heurística faturável
_lib/agenda.js 📅 token do Graph, 🔒 trava do ticket de reunião (Supabase, pela chave comum iCalUId) e ⏰ alerta de fim do dia ao organizador
tempo.js atividade.js vencimentos.js projetos.js usuarios.js (leitura)
config.js resumo.js teams.js (config/IA/cron/bot do Teams)
apontar.js transicao.js criar.js reunioes.js (escrita c/ token da pessoa)
public/
index.html só o HTML (head, cabeçalho, navegação, contêineres) + as tags <script defer>
css/app.css toda a folha de estilo (tema claro/escuro, componentes, telas, ajustes iOS)
css/mobile.css 📱 a camada do celular/tablet: SÓ media queries, um bloco por grupo de telas — ver "Celular e tablet"
js/NN-nome.js o painel, dividido em 36 módulos por domínio — ver tabela abaixo
sw.js service worker (rede primeiro; /js e /css com cópia para o offline)
portal.html área do cliente (AMS): login por conta (?pcli=) ou link por contrato (?c=)
scripts/
check-syntax.mjs sintaxe de api/** e public/js/** + ORDEM DE CARREGAMENTO dos módulos
check-entrega.mjs gate de entrega (Novidades + Roadmap revisado, vercel.json, mobile.css) — ver CLAUDE.md
check-roadmap-commits.mjs todo commit que mexe no app revisa o 🗺️ Roadmap (commit a commit do PR; avisa antes do commit)
check-guias.mjs gate dos 🧭 guias e do 🗺 tour: toda tela com guia, no tour completo e na ❓ Ajuda; seletores
dos passos vivos; GUIAS_REV acompanhando a última novidade — ver CLAUDE.md
mobile-audit.mjs 📱 auditoria do celular (iPhone emulado) tela a tela — local, não roda na CI
public/js/)O painel é JavaScript puro, sem bundler: os arquivos são scripts clássicos carregados
nesta ordem pelo index.html (<script defer src="/js/…">), num escopo global
compartilhado — const/let/function de nível superior ficam visíveis para os arquivos
seguintes. A única regra é: código que EXECUTA no carregamento (uma chamada solta, um
addEventListener, uma IIFE, const x = f()) só pode usar o que já foi declarado em
arquivos anteriores. npm run check confere isso com um parser real (acorn) e reprova
o PR se alguém quebrar a ordem — por isso dá para dividir sem medo.
| Arquivo | O que tem |
|---|---|
01-nucleo.js | estado, paleta, config compartilhada (cfg, Supabase; conflito → mescla de 3 vias cfgMescla3, item a item pelo id), helpers (esc, fmtH, toast, tooltip), datas/feriados, carrega(), filtros, agrega() |
02-projetos.js | 📁 Visão por Projetos (consolidado + ficha com a 🦴 espinha do projeto — projEspinha/projEspinhaVai: chips Contrato · Parceria · Plano · Cronograma · Execução · Apuração · Resultado que abrem a tela da outra área já no projeto, fases 3–4) e o caminho de volta (projAbreFicha + handler global [data-proj-ficha], projChipsFicha para as listas de projetos das outras telas) |
02b-cronograma.js | 📅 Marcos e Cronograma (R04): Gantt em cascata dos épicos com planejado × real, marcos, previsão pelo ritmo, dependências "blocks", detalhe do épico (burn-up, horas por mês, composição) e CSV |
03-agenda-reunioes.js | 📅 Agenda do Outlook → ticket de reunião, 🔁 séries recorrentes, 🎫 Controle de tickets separado por responsabilidade (agClasse, agIgnorado, agAguardando), convites de apontamento aos internos + ➕ a quem não está no convite (agPessoasConvite, agExtraCandidatos), renderAgenda |
04-meu-planejamento.js | 📋 Meu Planejamento (plano semanal, aprovação, relatórios planejado × realizado, drag-and-drop) |
05-novidades-roadmap.js | ✨ NOVIDADES/NOV_VER e 🗺️ ROADMAP/ROADMAP_REV — os dados que toda entrega atualiza |
06-prioridades.js | 🎯 Prioridades do time + Modo reunião |
07-graficos-cards.js | gráficos SVG (sparkline, área, donut, treemap, tsChart interativo) e cards base |
08-inicio.js | 🏠 Início (⚡ Ações de hoje, ⏱ Apontamento do time, 🧭 Radar), Visão Geral, Resumo, IA, folga |
09-qualidade-auditoria-meudia.js | ✅ Qualidade (IA), 🕵️ Auditoria, 📍 Meu dia |
Para acrescentar um módulo: crie public/js/NN-nome.js, adicione a tag <script defer> na
posição certa do index.html e rode npm run check (ele acusa arquivo sem tag, tag sem
arquivo, nome global duplicado e uso antes da declaração).
Pedido: "toda sexta-feira um relatório de planejado × orçado, reuniões, tickets criados e qualidade da informação, comparável com as semanas anteriores e com as atividades de cada pessoa — e uma visão para o gerente de projetos ver o que está parado e vencido por pessoa e cobrar com clareza". Decisões do usuário: orçado = horas vendidas da Rentabilidade (por projeto, pro rata dos dias úteis) + capacidade por pessoa; destino = canal de gestores + mensagem direta a cada gerente + tela no app por papel; gerente = campo no app (líder do Jira como sugestão); reuniões pelo Jira (nada de ler agenda).
Como funciona. No dia marcado (cfg.relSemanal = {ativo, dia:5, hora:'16:00', parado:5}, seção 📣
Envios automáticos da ⚙️ Central; se a sexta é feriado, o último dia útil da semana) o cron de 30 min
(apontamento-teams.yml → /api/teams?cron=1) roda o ?tipo=semanal em duas fases, para caber nos
60 s da função: foto (api/_lib/semanal.js → montaFotoSemanal: 8 fontes em paralelo — planos
enviados/aprovados do Supabase, worklogs do Clockwork (seg→dom), criados/concluídos por JQL
created/resolved (nunca updated), abertos com os parados primeiro (buscaAbertos({ordem:'asc'})),
atividade, usuários, catálogo, horas vendidas — e grava jirainsight_config id semanal_<segunda>, 104
semanas, anotando realFechado na foto anterior) e envio (IA opcional gravada na foto + cartão no canal
de gestores TEAMS_GESTORES_WEBHOOK_URL + mensagem direta a cada gerente via TEAMS_DM_WEBHOOK_URL, com
reenvio só para quem falhou; marca teams_estado.ultimoEnvioSemanal). Se o estado do Teams não puder ser
lido, o tique é pulado (nada é refeito nem reenviado). Exige CRON_SECRET (503 sem ela, mesmo em
dry=1, porque a foto tem dados por pessoa). Prévia manual: ?tipo=semanal&fase=foto|envio&dry=1 com o
Bearer; sem dry só com &confirmar=1.
Indicadores (segundos; % em 0–100): planejado = itens dos planos enviados/aprovados (maior versão);
orçado por projeto = horas vendidas do plano da Rentabilidade rateadas pelos dias úteis da semana
(api/_lib/rentab.js, porte fiel de rpVendPorMes/rpVendAte, com gate de paridade
scripts/check-rentab-paridade.mjs no npm run check); capacidade por pessoa = meta/dia × dias úteis −
ausências (com cfg.vigencias); execução, aderência, sem plano, não feito, consumo; reuniões pelo Jira
(tickets de reunião criados, horas em reunião, reuniões vencidas abertas); criados/concluídos/saldo; nota
de qualidade 0–100 (apontamento 40 · tickets abertos 45 · plano 15, sempre com os componentes);
vencidos/parados/sem data/sem responsável/sem estimativa/descrição curta (as regras dos ANL_CHECKS);
atividade por pessoa. Tudo em api/_lib/semanal.js, funções puras + orquestradora.
Quem vê. GET /api/config?semanal=1 confere a identidade no Jira e decide o papel no servidor
(papeisDe): gestor/negócio/diretoria/admin veem tudo; os demais veem o time + a própria linha + os
projetos onde são gerentes (sem porPessoa de outros e sem o texto da IA). O POST da config recusa
(403) alteração de papeis/gestores/projGerentes por quem não é gestor/admin pela config atual.
Variáveis na Vercel: CRON_SECRET (obrigatória para o semanal), TEAMS_GESTORES_WEBHOOK_URL (canal só
de gestores), TEAMS_DM_WEBHOOK_URL (fluxo do Power Automate para mensagem direta — o mesmo dos convites).
O painel virou mobile friendly de verdade em 2026-09-28, a pedido do usuário (as capturas do iPhone mostravam a página inteira encolhida). O desenho tem três peças:
public/css/mobile.css carrega depois do app.css e só tem regras dentro de
@media (max-width:…) / (pointer:coarse). É isso que garante que o computador não muda
um pixel — provado por diff de screenshot das 38 telas a 1360px contra a main (mesmo dia,
senão a data muda o timesheet). O arquivo tem as seções 1–3 (base: guarda .wrap{overflow-x:clip},
cabeçalho em 2 linhas, campos com 16px no toque, botões flutuantes redondos, tablet 761–880px) e
depois um bloco por grupo de telas (/* ---- 📱 grupo: inicio ---- */ … /* ---- fim: inicio ---- */,
pessoal, apontar, projetos, time, analises, negocio, config, global). Tela nova = regras no bloco do
seu grupo; nunca no app.css, nunca fora de @media.flex-wrap), tabela
larga rola dentro do próprio contêiner (.scroll-x), largura fixa vira max-width:100%.scripts/mobile-audit.mjs <url> [larguras] [vistas] [dirShots] emula um iPhone (isMobile, toque,
DPR 3) e mede por tela: elementos cortados na borda (ofens — a guarda não conta como recorte;
meta 0), alvos de toque < 44/32px, textos < 12px e campos < 16px (zoom do iOS). Sai com erro
quando há corte, então serve de gate local. Precisa de Playwright/Chromium e de um servidor com
dados (env PLAYWRIGHT, CHROMIUM, IDENT); não roda na CI.npm run check reprova regra do mobile.css fora de @media, @media sem condição de
celular/toque e a ordem errada dos <link> no index.html.Resultado da entrega: 38 telas × 393/360px com 0 elementos cortados, alvos de toque muito
pequenos 1.758 → 437 (os que sobram são links de texto em tabelas e rótulos decorativos), campos que
davam zoom 70 → 0, iPad em pé (768/820px) sem vazamento. Limitações conhecidas: tabelas numéricas
largas (Catálogo, Uso, planner da Rentabilidade) rolam de lado por desenho; algumas regras usam
:has() (iOS ≥ 15.4 — aparelhos antigos caem no leiaute empilhado, nada quebra).
Sem build não há bundler para pegar erros: rode
npm run check(ou deixe a CI rodar) para validar sintaxe, ordem de carregamento e a entrega (Novidades/Roadmap) antes de publicar.
| Variável | Obrigatória | Descrição |
|---|---|---|
JIRA_BASE_URL | sim | https://dexterityit.atlassian.net |
JIRA_EMAIL | sim | e-mail da conta admin do Jira |
JIRA_API_TOKEN | sim | token de API do Jira |
CLOCKWORK_API_TOKEN | sim | token da API do Clockwork (Pro) |
CACHE_TTL_MIN | não | minutos de cache no servidor (padrão 20) |
NAO_FATURAVEL_REGEX | não | regex que marca o tipo como não faturável — testada no nome e na descrição do tipo de issue (padrão n[aã]o.{0,4}fatur, que cobre "não faturável", "não-faturavel" e "não é faturável") |
JIRA_CF_CHAMADO_CLIENTE | não | id do campo Jira "Número do Chamado Cliente" exibido na aba AMS (padrão customfield_10270) |
JIRA_CF_CAUSA_RAIZ | não | id do campo "Causa Raiz" usado nos relatórios da aba AMS (padrão customfield_10759) |
JIRA_CF_PRODUTO | não | id(s) do campo "Produto" (relatórios AMS) — aceita vários separados por vírgula, usa o 1º preenchido (padrão customfield_10766,customfield_10760) |
JIRA_CF_PROCESSO | não | id(s) do campo "Processo" (relatórios AMS) — aceita vários separados por vírgula, usa o 1º preenchido (padrão customfield_10765,customfield_10761) |
SUPABASE_URL / SUPABASE_ANON_KEY | não | config compartilhada (jirainsight_config), convites de reunião (jirainsight_convites) e idempotência da folga-sync (jirainsight_folgas) |
TEAMS_WEBHOOK_URL | não | webhook do canal: relatório diário de apontamento e aviso de convites de reunião |
CRON_SECRET | não | se definida, /api/teams exige Authorization: Bearer <segredo> (use o mesmo valor no secret do GitHub Actions) |
Reuniões que valem para várias pessoas: quem organizou usa 👥 Apontar reunião
p/ várias pessoas na aba ⏱ Apontar (o próprio worklog sai na hora) e os demais
confirmam o convite com 1 clique — o worklog de cada um é criado com o token
da própria pessoa, preservando o modelo de segurança. Os convites ficam na tabela
jirainsight_convites do Supabase (colunas: grupo, issue, resumo, segundos, inicio,
comentario, criado_por, account_id, nome, status pendente|confirmado|recusado|direto,
worklog_id, erro).
📝 Ticket da reunião (Agenda, 2026-10-01): ao abrir o modal de uma reunião da Agenda, o painel confere no
Jira se já existe um ticket com o mesmo título no mesmo dia (POST /api/reunioes {conferir:1, itens:[{titulo,dia}]} — vencimento ou criação no dia, prefixo "Reunião:" ignorado) e, se existir, abre em
🔗 Vincular com a chave preenchida e diz quem criou; sem ticket no dia, abre em 🆕 Criar ticket novo.
As horas do convite têm três fontes: 📅 estimada (duração agendada), ⏱ real (relatório de presença do
Teams, consultado na hora; exige OnlineMeetings.Read.All + OnlineMeetingArtifact.Read.All de aplicativo e a
política de acesso a reuniões do Teams) e ✎ manual (digitada: 1h30, 45m). O valor é conferido antes de
criar ou vincular.
🙋 Você aponta na mesma confirmação + ✅ ticket para Feito (2026-10-23, JI-153): a linha "você" da lista
"Quem aponta" é uma caixa real ([data-agm-eu]), marcada por padrão — ao confirmar, você entra em pessoas e o
criarConvites do api/apontar.js cria o seu worklog na hora, com o seu token (proprio na resposta); o
painel então faz registraExtraDia + logAcao('apontar'). Reunião futura → caixa desabilitada; 👥 convidar de
reunião que já tem ticket → nasce desmarcada (as horas já saíram). O planGate roda antes de criar o ticket
quando você está marcado. Depois, agConcluiReuniao(key) (03) lê POST /api/transicao {listar:true} — que agora
devolve também o status atual — e, só se o status for de reunião agendada (/agendad|marcad/i), executa a
transição de categoria done cujo nome não é "cancelar" (no COP: "Participar da Reunião" → Feito). O mesmo
helper roda quando um convidado confirma o convite (13) e nas 📞 Ligações (14c). Teste: agenda-eu-test.mjs
(Playwright, 29 casos).
➕ Convidar quem não está no convite (2026-10-08): a lista de convidados do modal nasce dos participantes do
evento casados com os usuários ativos do Jira (por e-mail), mas quem participou sem estar no convite do Outlook
(entrou pelo link, foi chamado na hora, ou é o 21º convidado — o painel leva só os 20 primeiros do convite,
.slice(0, 20) no api/reunioes.js) também precisa apontar. O bloco ➕ Alguém da Dexterity que não está no
convite? procura pelo nome (sem acento) ou e-mail entre os usuários ativos do Jira (agExtraCandidatos: a lista
já vem só com pessoas ativas — /api/usuarios → jiraUsuariosAtivos; o RE_EXCLUIR é só garantia —, fora quem
já está coberto: você, os convidados casados e os extras já adicionados). Mostra até 8 chips e avisa "… e mais N"
quando há mais; o 1º chip fica destacado (↵) e é o que o Enter adiciona; e-mail de outro domínio aparece no
próprio chip; Esc com texto limpa a busca (com o campo vazio fecha o modal, como sempre). A pessoa entra na lista
marcada com o selo fora do convite (✕ tira), a linha é trazida à vista e um aviso confirma; a busca continua
enquanto houver resultados. Os extras vivem só no modal (_agM.extras) e vão em pessoas do
POST /api/apontar {convidar:true} como os demais, sem e-mail — o servidor resolve o e-mail do aviso
individual no Teams pelo accountId — e o 🗒 Histórico de ações mostra agenda-convidar-extra com o ticket e, em
"De → Para", quem entrou fora do convite. Redesenho atrasado (conferência no Jira, ⏱ real, projetos, usuários) só
acontece se o 📝 ainda é o modal aberto (agAgModalAberto) e devolve o foco/cursor a quem estava digitando; falha
ao ler os usuários mostra "↻ tentar de novo" em vez de tratar todo mundo como externo.
Pedido: "qualquer ligação que aconteça no meu computador — WhatsApp, Teams, Google Meet —, assim que acabar, um pop-up para criar um ticket daquela reunião ou apontar minhas horas num ticket já criado" (como o Call Timing do Timing). Duas peças:
public/call-tracking/CallTracking.swift, app de barra de menus 📞, macOS 12+). Pelo Core
Audio ele sabe quem está captando o microfone — no macOS 14.2+ pela lista de processos de áudio
(IsRunningInput + bundle id: Teams, WhatsApp, Zoom, FaceTime, Slack, navegadores…; os seletores vão pelo código
de 4 letras, para compilar também com o SDK do macOS 12/13). Antes do 14.2 só sabe que o microfone PADRÃO está em
uso e supõe o app de chamada aberto ("app provável"): o palpite fica fixo enquanto o microfone segue em uso (trocar
de janela não "troca de chamada"), sem app de chamada aberto não é chamada, e em fone com microfone (que também
toca música) só vale se o app de chamada está na frente quando o aparelho liga. Nunca ouve nem grava.
Ligação no navegador: com a permissão de Automação, lê o endereço e o título das abas para reconhecer
meet.google.com/xxx-xxxx-xxx, Teams web, WhatsApp Web, Zoom web — a aba ATIVA e a janela da frente primeiro;
Teams/WhatsApp web só contam na aba ativa (ficam abertos o dia todo); a 1ª aba de chamada achada dá o nome. Regras da máquina de estados (Rastreador,
testada no --selftest): microfone solto por menos de 20 s é a mesma ligação, menos de 2 min (configurável) é
descartada, Mac que dormiu fecha a ligação no último instante visto. Quando a ligação acaba, um pop-up (NSPanel
flutuante) oferece 📝 Criar ticket · ⏱ Apontar em ticket · Depois · Ignorar · Ver todas; o botão abre
/?v=chamadas#ct=<base64url(JSON)>&m=criar|apontar|lista no navegador padrão (ou numa janela pop-up do
Chrome/Edge, pelo menu). As "Depois" ficam no menu 📞 (até 50, 14 dias); as já entregues ficam 14 dias para
Reenviar ao painel (o navegador abriu noutro perfil do Chrome, a janela fechou antes de carregar — o painel junta
por id e não duplica). Preferências no menu: duração mínima, apps que nunca perguntam (vale também no meio da
ligação), ler abas, som, início automático, pausar 1 h e um diagnóstico de quem usa o microfone.14c-chamadas.js). Um <script> no <head> tira o fragmento da URL antes de
qualquer outro script (inclusive o Analytics da Vercel) e o guarda no sessionStorage: a ligação nunca vai ao
servidor. O 14c valida cada item (id, textos curtos, ligação já encerrada e de até 30 dias — link forjado com data
no futuro não entra), junta ao localStorage (jirainsight_chamadas_v1, por id — o que já foi apontado ou ignorado
não volta; o que chega nunca despeja uma pendente guardada) e abre o modal: ligação que coincide com uma reunião da
Agenda (maior sobreposição, ontem a +14 dias) puxa o título, sugere o ticket da reunião (vínculo da Agenda ou a
mesma conferência "mesmo título, mesmo dia") e oferece ligar o ticket ao evento (). :
sugestões (ticket da reunião, seus recentes de 14 dias, abertos parecidos), projeto + busca nos abertos (mesma
rota e cache da 🎫 busca) ou chave digitada. : projeto, título, tipo Reunião do projeto, atribuído a
quem cria, labels (+ ). O worklog nasce no da ligação — o
ganhou o campo opcional (, São Paulo); trocando o dia, entra às 09:00. Se o ticket foi
criado e o apontamento falhou, a ligação guarda e confirmar de novo (ignorar e voltar
também não perde o parcial). Várias ligações:
(fila; o "Ver todas" do Mac abre o mesmo). Histórico: e
.Desde 2026-09-20 o 🤝 contrato de parceria (📑 Contratos › 🤝 Parceiros › gaveta 🧾 Faturamentos) é o dono da ordem de venda no Odoo — o pedido foi "editar o contrato e ver a modificação refletida no Odoo, com o número da ordem à vista, um item por faturamento, horas extras num processo próprio e cada nota podendo ter mais de um objeto de resultado". Como funciona:
ODOO_PRODUTO_SERVICO, o produto
escolhido em ⚙ Produtos ou o 1º serviço à venda) + um item por ➕ hora extra (produto ODOO_PRODUTO_EXTRA
ou o mesmo). Quantidade = horas previstas (equipe + ✎ ajuste manual), preço = valor-hora; um mês com valor
fechado e sem horas vira 1 × valor.account.analytic.account, lidas por
POST /api/resumo?acao=odoo-catalogo). O rateio padrão do contrato (ex.: 90% Sumitomo · 10% Hi-Mix), um rateio
próprio por período e um objeto por hora extra vão como analytic_distribution de cada item. Sem o campo
(módulo analítico ausente) a ordem é gravada sem rateio e o painel avisa.?acao=odoo-venda-status: estado, faturado/pago por item, faturas) + comparar
(?acao=odoo-contrato com dry:1): o servidor devolve o plano (criar · atualizar · apagar/zerar · mantidos ·
itens feitos à mão) e a tela mostra tudo antes do Aplicar. Regras (no servidor): item já faturado nunca muda;
a quantidade nunca cai abaixo do já faturado; preço só muda antes de qualquer fatura; o que saiu da previsão é
apagado na cotação e zerado na ordem confirmada (o Odoo não deixa apagar); item criado à mão no Odoo fica.?acao=odoo-contrato-cancela, action_cancel com
disable_cancel_warning; se o Odoo recusar — faturas lançadas —, a pessoa decide se remove mesmo assim)./myself, nunca persistida) e usa a conta
de serviço do Odoo. Desde 2026-09-30 (pedido do usuário) qualquer pessoa identificada edita os contratos de
parceria e cria/aplica a ordem — PC_SO_GESTORES em 16b-parcerias.js religa a restrição aos gestores. O vínculo (c.odoo.itens: chave p:AAAA-MM / x:<id> → id da
linha) fica na config compartilhada.Pedido do usuário: "eu tenho um contrato e eles me alocam em um projeto; eu faturo por hora, independente se eu gastei horas lá ou não — posso passar tarefas internamente ou usar algum acelerador; quero projetar essas alocações por projeto e ter previsibilidade do meu fluxo de caixa; Hi-Mix e Sumitomo eu faturo mensalmente; Axia é atendimento de AMS; integrar com o Odoo para o fechamento e a emissão da nota serem consistentes". O ciclo ficou assim:
Pedido do usuário: "informar manualmente as datas de início e fim de cada ciclo de faturamento, sem limitar ao mês-calendário (25/08 a 25/09); para cada projeto, dentro de cada ciclo, informar manualmente as horas previstas de cada dia — a previsão distinta do apontamento; vincular a alocação do projeto no ciclo à consultoria parceira; o faturamento por consultoria, mas o objeto de custo enviado ao Odoo identificado por projeto".
pcCiclosLista, pcCicloAjusta). O modelo continua o mesmo — o ciclo AAAA-MM é
definido pelo fim (c.ajustes[ym].fecha) e começa no dia seguinte ao fim do anterior —, então editar o início
grava o fim do ciclo anterior; datas que se cruzam são recusadas (os ciclos são contíguos).c.diario[frente][AAAA-MM-DD] = h (0 também vale). Linhas = os dias do
ciclo (úteis; fins de semana e feriados ao pedir), colunas = os projetos; o automático dos trechos/estimativa é o
placeholder; ✎ dias úteis preenche o ciclo; ↺ limpa. pcFrPrevisto = automático + Σ(digitado − automático
do dia) é a única porta da previsão (grade por ciclo, modal ✔ apurar, Odoo, 🧾 próximas notas, 💰 caixa,
💹 Rentabilidade). A ✔ apuração (c.apur) é outra coisa e manda na nota quando existe. Dia digitado num ciclo sem
alocação cria o ciclo no planejamento. Leitores (pcDiario, pcDiaMan) não criam o objeto; ⧉ duplicar não leva.projEspinhaDados) acha a consultoria pelo 🗂 projeto (pcFrenteDoProjeto) antes do plano.pcSemObjeto
(16c) avisa no 🧾 Faturamentos o projeto que iria ao Odoo sem objeto próprio (sai com o rateio padrão).Contrato sem projetos continua exatamente como antes (um item por período, ✎ ajuste manual em c.prev).
Na aba Resumo, a tabela Por pessoa mostra a coluna Meta (horas apontadas ÷
meta esperada no período). Acima de 100% não é problema — é sinal de quem trabalhou
além do previsto, então fica em verde. Quando alguém acumula, em média, mais de
6h extras por semana (extra = horas − meta esperada, normalizado pelo nº de semanas do
período), aparece o botão 🌴 Folga. Ele abre um mini-formulário (data + meio período /
dia(s) / nº de horas) e chama POST /api/resumo?acao=folga (consolidado nessa rota por
causa do limite de 12 Serverless Functions do plano Hobby), que cria uma solicitação
de Time Off no Odoo (hr.leave) para a pessoa — localizada pelo e-mail de trabalho
(ou pelo nome, como fallback). Usa a API externa do Odoo (JSON-RPC) com uma conta de serviço.
Requer ODOO_URL, ODOO_DB, ODOO_LOGIN, ODOO_API_KEY e ODOO_FOLGA_TIPO_ID (id ou
nome do tipo de ausência). Sem essas variáveis, o formulário avisa que falta configurar.
Quando uma ausência é aprovada no Odoo (hr.leave em state = 'validate'), uma
automação cria 1 ticket no Jira por dia útil da folga, atribuído à pessoa, com
data limite = o dia e o campo Departamento Dexterity preenchido; e registra o
worklog no nome da pessoa (via Clockwork). Multi-dia vira vários tickets (1 por dia útil,
pulando fim de semana/feriado); meio período/horas = 1 ticket. Idempotente por
leave_id + dia.
Endpoint: GET /api/resumo?acao=folga-sync (consolidado para respeitar o limite de 12
funções; lógica em api/_lib/folgaSync.js). Como cria registros, exige CRON_SECRET
(sem ele, responde "não configurado"; com header errado, 401). ?dry=1 simula sem criar nada.
Agendamento: GitHub Actions .github/workflows/folga-sync.yml (a cada 15 min, dias
úteis). Como é idempotente, rodar com frequência é seguro. Defina o secret CRON_SECRET
(e, opcional, FOLGA_SYNC_URL) no repositório, iguais aos da Vercel.
Mapeamento pessoa Odoo↔Jira: por e-mail (work_email do Odoo = e-mail do Jira →
accountId). Exceções: JIRA_FOLGA_MAP (JSON {"email":"accountId"}). Sem accountId,
o dia fica sem_mapeamento (sem criar) e é reprocessado quando o mapeamento existir.
Worklog: requer CLOCKWORK_ESCRITA=1 + CLOCKWORK_API_TOKEN (apontamento sai no nome
da pessoa). Horas/dia = jornada do Odoo (resource.calendar.hours_per_day), meio período =
metade, horas específicas = request_hour_to − request_hour_from.
Tickets: criados pela conta de serviço (JIRA_EMAIL/JIRA_API_TOKEN) e atribuídos
à pessoa (não há token individual numa automação).
Estado/idempotência (Supabase): crie a tabela jirainsight_folgas:
create table if not exists jirainsight_folgas (
id text primary key, -- "{leave_id}:{YYYY-MM-DD}"
leave_id bigint,
dia date,
email text,
account_id text,
issue_key text,
worklog_id text,
horas numeric,
status text, -- criado | processando | sem_mapeamento | erro
erro text,
tentativas int not null default 0, -- nº de tentativas que falharam (teto evita retry infinito)
created_at timestamptz default now(),
updated_at timestamptz default now()
);
v1: a automação apenas cria os tickets. Reversões no Odoo (recusa/cancelamento ou edição de datas de uma folga já aprovada) não cancelam/ajustam os tickets — fica para uma fase 2. Como "qualquer ausência aprovada" vira ticket, na primeira execução rode com
?dry=1para conferir o volume antes de ligar o cron.
Aba Visão Geral (a tela inicial do painel): responde, num só lugar, "como está a entrega?" — reaproveitando os dados que já temos (sem nova função serverless):
Cada bloco é acionável: clicar leva direto à aba de detalhe (Alertas, Timesheet…).
Motor de séries temporais premium feito à mão em SVG (sem dependências/CDN — tsChart()),
estilo "Bloomberg":
prefers-reduced-motion. Toque suportado.Treemap interativo (treemap(), squarified) para distribuição — área proporcional ao valor,
rótulo por célula, realce no hover e clique para drill-down. Usado em Top projetos por horas e
Atividade por tipo (Resumo) e na classificação por causa (portal do cliente).
Complementam: donut/medidor para metas e barras com realce (a barra focada destaca, as demais esmaecem) clicáveis para drill-down.
O botão de tema no topo (☀️/🌙/🟢) abre um menu com três temas. A preferência fica salva em
localStorage (jirainsight_theme: light · dark · dexterity) e é aplicada cedo no
<head> para evitar "flash". Implementado por variáveis de tema ([data-theme="dark"] e
[data-theme="dexterity"] sobrescrevem --offwhite/--grafite/--cartao/--linha/--muted/ --trilho/--titulo/--corpo/…), então todas as telas, tabelas, gráficos e campos adaptam as cores.
#1B1B1B,
texto off-white, cerceta #009994 como acento, títulos em Barlow Condensed em caixa
alta, texto Figtree, rótulos em IBM Plex Mono, cantos retos e superfícies chapadas
com fio fino. As fontes só são baixadas (Google Fonts) quando esse tema está ativo. O CSS
fica em dois lugares de public/css/app.css: as variáveis logo após o modo escuro e os
componentes no fim do arquivo. O PDF continua saindo claro em qualquer tema.Na Visão Geral, um score 0–100 (donut + componentes) resume a saúde da operação: média ponderada de Apontamento (40%), Prazos (vencidos/vencem hoje, 30%) e Alertas críticos (30%). Faixas: ≥80 Saudável · 60–79 Atenção · <60 Crítico. Cada componente é clicável e leva ao detalhe. (O componente de AMS saiu do score — AMS é exclusivo da aba AMS & Governança.)
Botão 🖨 PDF no topo gera um relatório da tela atual via impressão do navegador
(Salvar como PDF): um @media print força o tema claro, esconde a navegação/controles e
adiciona um cabeçalho (logo + título da visão + período + data/hora), evitando quebras no meio
dos cartões. Funciona em qualquer aba (ideal na Visão Geral).
#ic-*) em vez de emojis, para um visual mais
profissional. O grupo da aba ativa fica sublinhado; o selo de convites aparece também no
cabeçalho de Operação.public/logo.svg) — clicável, volta para o início (Visão Geral).localStorage)./api/vencimentos devolve tIcon/prioIcon).Na aba Timesheet, o total de horas de uma pessoa num dia (qualquer célula com horas) é
clicável — abre um modal com as horas por projeto daquele dia; cada projeto expande
(<details>) para os chamados que somam essas horas, com link ↗ para o Jira. A coluna
Total também é clicável e abre o mesmo detalhamento para o período inteiro. Tudo no
cliente, a partir dos worklogs já carregados (estado.tempo.worklogs + resumos), respeitando
os filtros atuais — sem nova chamada ao servidor.
No Ranking de apontamento, quem está em dia ganha um destaque comemorativo
(estrelinhas que cintilam + medalha pulsante, em dourado) e quem não apontou nada no
período ganha um efeito de decepção (cinza dessaturado + "chuvinha" + leve balanço). Tudo em
CSS e respeita prefers-reduced-motion (sem animação para quem pediu menos movimento).
O filtro global "Ocultar rotinas e reuniões automáticas" foi removido (não fazia sentido manter): as horas de rotinas/reuniões agora entram normalmente nas visões.
🏅 Scoreboard e quem saiu do time (2026-10-24): o placar guarda 120 dias de fotos por
accountId (cfg.scoreHist); quando uma pessoa deixa de ser usuária ativa do Jira ela some do
elenco (pessoasUnidas) e a linha mostrava "712020:…". Agora /api/usuarios devolve também
inativos (jiraUsuariosTodos em api/_lib/util.js; estado.usuariosInativos), cada foto
guarda o nome em cfg.scoreNomes (higienizado junto com as fotos) e nomePessoaDe(a) resolve
elenco → inativos → nome guardado → id. No placar, pessoaForaDoTime(a) manda a pessoa para o
fim com o selo "saiu do time" (.sc-fora), sem medalha. Teste: scoreboard-nomes-test.mjs
(12 casos).
Aba Admin: cadastro de clientes/contratos que destrava AMS e Receita. Para cada contrato: cliente, tipo (AMS / bolsa de horas / projeto / horas abertas), valor-hora (R$), vigência e os projetos do Jira mapeados ao cliente.
A lista de projetos segue o tipo (desde 2026-10-04; antes ela só mostrava AMS e os projetos de horas abertas nem apareciam): AMS sugere DAMS/PAMS, Horas abertas sugere DEA/PEA, Projeto sugere DEF/PEF e Bolsa todas as categorias de cliente (
CT_CATS). "Mostrar todos os projetos" abre as demais (arquivados só se já estavam no contrato) e a 🔎 filtra por nome/chave. O tipo, os marcados e as horas vivem emestado.admin.form— o que a busca esconde continua marcado e é salvo.
⏱ Horas abertas (2026-10-04): o contrato guarda o total contratado (horasContratadas,
obrigatório) e, quando os projetos existirem, a alocação por projeto (c.alocacao = {CHAVE: horas},
c.projetos = os projetos do contrato). Dá para salvar só o total e alocar depois (📌 alocar horas
no cartão); alocar além do contratado não salva. O cartão e a aba 💰 Bolsa de horas & projetos
mostram contratado × alocado × consumido × saldo por projeto, com o consumo lido desde o início da
vigência (/api/tempo?desde&ate, teto de 12 meses — ctConsumoHoras). ctHorasDoProjeto(c, chave)
é a régua de "horas vendidas" de UM projeto que a Controladoria, as Métricas, a 💹 Rentabilidade e a
ficha do projeto usam: AMS = horas do ciclo; horas abertas = o alocado ao projeto; bolsa/projeto = o total.
Para o tipo AMS há um bloco extra de parâmetros do contrato:
Na seção "Contratos cadastrados", cada contrato vira um card com os detalhes completos
num quadro legível: para AMS — apuração, valor-hora, horas/ciclo, parcela do ciclo,
valor anual (e h/ano), mín/teto mensal, início da vigência e banco de horas; para
bolsa/projeto — valor-hora, horas contratadas e vigência. Abaixo: projetos mapeados,
observações e um preview de consumo no período. Um contrato AMS sem projetos mapeados
recebe um aviso (ele não aparece na apuração da aba AMS até mapear os projetos). Fica salvo
em cfg.contratos (compartilhado via Supabase / /api/config).
🔖 ID e ⧉ duplicar (2026-10-03). Cada contrato tem um ID visível — CT-001… nos 🏢 Clientes
(c.cod, contador cfg.ctSeq) e PC-001… nos 🤝 Parceiros (cfg.pcSeq) — que nasce no cadastro e nunca muda;
o contador só sobe, então o ID de um contrato removido não volta para outro. Os contratos antigos ganham o ID numa
migração determinística (data de criação → ordem da lista; ID repetido por duas sessões cadastrando ao mesmo
tempo fica com o primeiro e o outro ganha o próximo), então duas sessões chegam aos mesmos IDs e a mescla da
config não vê conflito (ctCodigosPlano/ctGaranteCodigos/ctProxCod em 16-contratos-ams-receita.js). O
⧉ duplicar abre um contrato novo já preenchido (ctCopia / pcCopia) — nada é gravado antes do
"Adicionar"/"Cadastrar"; não copia o link do cliente (portalToken), nem, na parceria, a ordem do Odoo, as horas
extras, a previsão digitada e o histórico; a cópia guarda de qual veio (copiaDe).
O AMS é totalmente separado da Receita: tem aba própria (menu AMS & Governança → AMS).
A aba Receita ficou só com bolsa de horas e projetos. Ambas consomem cfg.contratos +
worklogs, sem nova função serverless.
🛠️ Aba AMS — apuração por ciclo (banco de horas). Mostra um contrato AMS por vez:
um seletor de cliente escolhe o contrato e a visão é sempre por ciclo (alinhado ao mês
de início da vigência). Os worklogs do ciclo selecionado são buscados via
GET /api/tempo?desde=&ate= (independente do período do topo). Mostra:
/api/tempo devolve w.pk (chave do pai) em cada apontamento de
sub-tarefa e o mapa chamados = {chave: {t, f, pk}} com tipo/faturável do pai, que entra em
resumos/infos mesmo sem apontamento próprio (jiraResolveIssues resolve o pai no 2º nível).
No painel, amsIndice(c, cyc, ym?) é o único agrupador: um grupo por ticket principal, com
o tipo e os campos (Causa raiz/Produto/Processo) do pai, nSub e as linhas dizendo de qual
sub-tarefa vieram. Regra do faturável: sub-tarefa de ticket não faturável é não faturável
(f = próprio tipo && tipo do pai, no servidor).O banco de horas vale dentro do ciclo e não acumula para o próximo. Só as horas faturáveis consomem o pacote/excedente; as não faturáveis (ex.: "Tarefas ADM") ficam fora da apuração — continuam visíveis como indicador (split/KPI) e na memória do PDF, mas não entram no banco de horas nem no excedente.
💰 Aba Receita — bolsa de horas & projetos. Para contratos de bolsa/projeto fechado, projeção pelo período selecionado: horas contratadas × consumidas, % de esgotamento + projeção (ritmo dos dias úteis fechados até o fim do período), receita estimada e % faturável. (O quadro "horas sem contrato" foi removido desta visão.)
Página pública /portal.html?c=<token> para o cliente acompanhar as horas do contrato
AMS, somente leitura e escopada por token (cada contrato tem o seu link). O acesso e a
filtragem dos dados são no servidor: o token identifica o contrato e a API só devolve os
dados daquele cliente — nunca de outros.
portalToken, salvo em cfg.contratos); botão copiar e novo (invalida o anterior).GET /api/config?portal=<token> (consolidado no endpoint de config — sem nova
função serverless): lê o contrato no Supabase, busca worklogs do ciclo (Clockwork) e
contagens do Jira só dos projetos do cliente.Desde 2026-09-26, além do link por contrato, o /portal.html tem login: cada pessoa do
cliente tem a sua conta, o que o link secreto não dá (ele vaza, não se revoga por pessoa e
não registra quem entrou). Serve como página própria e embutida num iframe do site.
jirainsight_portal_contas no Supabase — nunca a config compartilhada, que
vai inteira para o navegador de todo o time. Senha só em hash scrypt (salt por conta).PORTAL_SEGREDO; sem a env var,
deriva da chave do Supabase), validade de 12 h, enviado no cabeçalho Authorization e
guardado no localStorage — não em cookie, que é o que faz funcionar dentro de um iframe
de outro domínio. A conta é revalidada a cada pedido: revogar corta a sessão aberta./api/config?pcli=<ação> — login, convite, definir, dados, trocar-senha e,
para o gestor (headers x-jira-*), contas, convidar, revogar, reativar, remover.
Sem função serverless nova (o limite de 12 da Vercel).vercel.json define frame-ancestors para /portal.html — é ali que se
acrescenta o domínio do site que vai hospedar a área. Também manda Referrer-Policy: no-referrer, para o token do convite não vazar no cabeçalho Referer.Atenção (deploy): o projeto na Vercel está com SSO Protection em
all_except_custom_domainse hoje só tem endereços.vercel.app— o cliente externo esbarra no login da Vercel antes de chegar ao portal. Para a área funcionar é preciso um domínio customizado (ex.:portal.dexterityit.com.br), que é a exceção da regra. O mesmo vale se algum dia a Password Protection global for ligada: ela bloqueia tudo.
Pedido: "um portal onde meu cliente vê o progresso do projeto, decisões a serem tomadas, pendências, riscos, atraso, time, FAQ, canal do Teams, próximas reuniões, links úteis, pasta do SharePoint — sem dar acesso ao Jira — e um módulo no Hub exclusivo para isso". Decisões do usuário: acesso amarrado ao contrato (a conta da área do cliente continua a mesma; várias pessoas do cliente = várias contas), reuniões do calendário do projeto no Microsoft 365, progresso pelos épicos e marcos (a mesma conta do 📅 Cronograma), visual padrão Dexterity.
O que o cliente vê (/portal.html, modo projeto quando o contrato tem projeto publicado; a aba
📞 Chamados e horas continua para contratos AMS; ?c= legado inalterado): cabeçalho cliente · projeto,
💬 Canal do Teams e 📂 Pasta em destaque, Progresso (%, marcos, próximo marco, maior atraso, semáforo,
previsão), Cronograma (barras por épico, ◆ marcos, linha de hoje), O que a Dexterity precisa de você
(tickets em status que citam o cliente + pendências cadastradas), Decisões a tomar (as da 🎯 Prioridades
marcadas como visíveis), Riscos, Próximas reuniões (30 dias, botão Entrar), Time do projeto, FAQ
e Links úteis. Cada bloco só aparece se estiver ligado e tiver conteúdo.
De onde vem. api/_lib/portal.js monta o payload por allowlist (ESQUEMA + assertAllowlist nos
testes): o cliente nunca recebe responsável por ticket, horas por pessoa, custos/valores, comentários,
descrições, e-mails, saude/inconsistencias, nem nada de outro cliente; bloco desligado é zerado no
servidor, não só escondido. Progresso/atraso: api/_lib/cronograma.js (porte de crLinhas/projMarcos
com gate de paridade scripts/check-cronograma-paridade.mjs no npm run check; a ficha do projeto passou
para api/_lib/projetos.js). Calendário: calendarioDoProjeto lê só a caixa/grupo configurado pelo gestor
pelo Graph (Calendars.Read de aplicativo; grupo do Teams exige Group.Read.All), só campos públicos (nunca
participantes/organizador/corpo), fuso de São Paulo, sem eventos privados/cancelados; e-mails de pessoas do
time são recusados e a caixa só vale depois de confirmada pela prévia. Cache do payload em jirainsight_config
id portal_<KEY> (10 min, sobrevive à partida a frio; ↻ do gestor invalida em todas as instâncias; falha do
banco/Jira → 502 e nada em cache).
Onde mora o conteúdo. Tabelas próprias (migração supabase/migrations/20260928_portal_projetos.sql):
jirainsight_portal_projetos (contrato, projeto, publicado, config jsonb) e jirainsight_portal_itens
(pendência · risco · faq · reunião, com responsável/lado, prazo, status por tipo, visível, ordem, quem/quando)
visivel_cliente em jirainsight_decisoes. Não vai para a config compartilhada (ela desce inteira para
o navegador de todo o time e tem teto de 256 KB).Módulo interno 🌐 Portal do cliente (16d-portal-cliente.js, Dexterity Entrega, ?v=portal&pproj=KEY&psec=…,
chip 🌐 na espinha do projeto): 🌐 Projetos (contrato × projeto, publicado/rascunho), ⚙️ Configurar (apresentação,
canal, pasta, calendário com "salvar e testar", links, time sugerido do Jira, épicos ocultos, blocos), 📋 Conteúdo
(tabela editável por tipo; decisões da 🎯 Prioridades ganham o interruptor "visível ao cliente"), 👤 Acessos
(o mesmo bloco dos contratos) e 👁 Pré-visualizar (iframe /portal.html?preview=KEY + postMessage com a
identidade do gestor). Só gestores editam; publicar/despublicar vai ao 🗒 Histórico.
Rotas (tudo em api/config.js?pcli=, sem função nova): cliente — dados (+ tipo, projetos[]),
projeto&p=KEY; gestor (validaJira + planEhGestor) — admin-projetos&ct, admin-config (trocar o
contrato de um projeto publicado exige confirmação: 409), admin-itens&p, admin-decisao, admin-sugestoes&p
(time sugerido, reuniões do Jira, líder, teste do calendário), admin-refresh&p, preview&p. Projeto fora do
contrato ou não publicado → 404 idêntico ao inexistente.
Pré-requisito de rede: domínio customizado portal.dexterityit.com.br (a SSO Protection da Vercel
bloqueia visitantes externos nos .vercel.app) — a cerca do host já está no código.
Aba Alertas: motor de alertas acionáveis montado a partir dos dados que já
temos (/api/vencimentos amplo + calcTimesheet), com severidade (🔴 crítico / 🟠 alto
/ 🟡 médio) e ação recomendada por item:
KPIs por severidade filtram a lista. Alertas de AMS, orçamento de projeto e SLA entram quando os dados (valor-hora, horas contratadas, cliente↔projeto) forem cadastrados.
No botão Metas → "Usuários externos (ocultar do time)" dá para marcar pessoas
(ex.: colaboradores/clientes externos) que não devem aparecer em nenhuma visão —
ranking, timesheet, resumo, seletores e no relatório do Teams. Fica salvo em
cfg.ocultos (compartilhado via Supabase); os dados dessas pessoas são removidos no
carregamento (removeUsuariosTecnicos) e no /api/teams.
No topo da aba, quem está identificado vê o painel ⏱ Meu timesheet com o total que já apontou em cada dia, destacando (⚠) e avisando os dias que passam da meta diária (padrão 8h, ajustável em Metas / por pessoa). Ao apontar, o painel atualiza na hora e o feedback diz o total daquele dia e avisa se ele excedeu a meta.
Base própria das minhas horas por dia (2026-10-20): o total do dia, o aviso de meta, o
painel 🗓 e os 🕑 tickets recentes leem estado.apontar.meusLogs — só os meus worklogs
de /api/tempo?desde&ate, dos últimos 14 dias e alargados para trás quando um dia mais
antigo é escolhido (apGaranteMeusLogs(dia)), lidos antes de gravar o apontamento.
Antes, tudo isso somava estado.tempo.worklogs — o período selecionado no topo (padrão
7 dias), que o Apontar nem mostra —, e apontar num dia fora dele dizia que o total do dia
era só aquele lançamento. Fora da janela o período do topo ainda vale; o acumulador
otimista (extraPorDia/extraLogs) soma por cima e é descontado quando a leitura seguinte
traz um worklog novo com o mesmo dia + ticket + segundos (um igual que já existia não o
engole). A releitura depois de gravar ou do ↻ vai sem o cache do servidor (nocache=1); o
pedido é limitado a 400 dias e a janela guarda o intervalo que o servidor devolveu; trocar
de usuário descarta a janela. Se o Clockwork não responder, a mensagem diz que o total não
foi conferido. A 🏠 Início lê a mesma janela (2026-10-24): o card ⏱ Suas horas de hoje
somava o período do topo (escondido na Início e podendo nem incluir hoje) e mostrava 0m com
2h35 no Timesheet; agora axGaranteMeusLogs(hoje) (08) garante a janela, o ↻ Atualizar a relê
sem cache (carrega() marca meusLogsSujo) e a falha da leitura vira "não consegui ler" no
próprio card. Teste: inicio-horas-test.mjs (13 casos).
Configuração dos gestores em ⚙️ Configurações › 📋 Meu Planejamento (cfg.planGate = {modo:'off'|'aviso'|'bloqueio', rascunho}, chave de papel: o api/config.js recusa a mudança
de quem não é gestor/admin). Com a regra ligada, lançar horas num dia que não está no seu
planejamento semanal enviado (plano da semana inexistente, em elaboração/devolvido sem a
opção rascunho, ou sem atividade naquele dia) mostra uma sobreposição própria (.pg-bg):
Avisar explica, registra apontar-sem-plano no 🗒 Histórico e deixa seguir; Bloquear
só oferece "📋 Ir para o Meu Planejamento" (já na semana certa). Fim de semana, feriado,
ausência (mpCapDia === 0) e dias antes do início da apuração (mpInicioVig) são dispensados.
A porta é uma — planGate(dias) em 04-meu-planejamento.js, que lê o plano com
planGateCarrega (aguardável, mesmo cache de mpGaranteMeu) — e todo lugar que grava worklog
próprio a chama antes do POST: ⏱ Apontar (linha, convite, 👥 em grupo), 🛠 Gestão, 🧾
Preciso criar meus tickets, 📞 Ligações, ➗ Rateio, 🔁 Transformar, 📍 Meu dia, 🎫 criação
rápida e 🔗 vincular. Falha ao ler o plano não bloqueia (toast e segue). É regra de tela: o
servidor ainda não a impõe (roadmap).
No bloco "seu dia": 🗂 Projetos da semana (projetos do MEU plano da semana, planejado ×
feito pela janela do ⏱ Apontar — substitui o card da alocação macro aposentada), 💬
Aguardando sua resposta (menções sem resposta + comentários de OUTRAS pessoas nos meus
tickets sem ação minha depois, pela atividade de 7 dias do Radar + plano devolvido; abre a
🛠 Gestão só nesses tickets via soKeys) e ⏳ Com o cliente (meus tickets em status
"aguardando cliente"). Nenhuma leitura nova: tudo vem do que a Início já carrega
(/api/vencimentos, /api/atividade, Inbox, plano da semana).
A pedido do usuário, o app inteiro foi auditado (44 telas, 9 fluxos da pessoa e do gestor, capturas no
computador e no celular, scripts/mobile-audit.mjs) e comparado com 36 ferramentas de mercado em seis
categorias (apontamento sobre o Jira — Clockwork Pro, Tempo, Everhour, Clockify —, apontamento automático —
Toggl, Timely, Memtime, Timing, Rize —, organização pessoal — Sunsama, Akiflow, Reclaim, Jira For you,
Linear, Asana —, capacidade do time — Float, Runn, ActivityTimeline —, rituais no Teams e IA aplicada).
O resultado está em docs/analise-mercado-e-melhorias-2026-10-20.md (resumo executivo, achados com
evidência arquivo:linha, tabela de mercado, padrões de UX, oportunidades priorizadas em três ondas e as
fontes) e virou 27 itens novos + 4 reescritos no 🗺️ Roadmap (revisão 2026-10-20.4), com um link
para o documento no topo da tela do Roadmap. As três ondas: apontar com menos atrito (cronômetro,
editar/desfazer, "continuar de onde parei", lembrete pessoal de fim de dia, rascunho do dia em cartões,
componente único de apontamento), organizar o meu trabalho (lista única dos meus tickets, plano ligado
a tickets, grade semanal, Início com ações no lugar e modo foco, atalhos, Hoje em linha do tempo, rituais)
e o time e a plataforma (bom-dia no Teams, config fatiada por perfil, abertura mais leve, alvos de
toque de 44 px, fechamento semanal do timesheet). O estágio de cada item é a sugestão da análise; o time decide.
Pedido do usuário: "transforme essa tela em algo totalmente editável" (?v=cronograma&cproj=KEY). A tela só lia o
Jira; agora tudo que ela mostra do épico se edita ali, com o token da própria pessoa (como ⏱ Apontar e 🛠 Gestão) e
gravando no Jira — a fonte continua sendo o Jira, sem modelo paralelo. Desenho em docs/cronograma-editavel-2026-10-21.md.
public/js/02b-cronograma.js, camada "✎ CRONOGRAMA EDITÁVEL"): arrastar a barra planejada move início
e marco (data-cr-bar), as bordas ([data-cr-h="ini|fim"]) mudam um só, o ◆ (data-cr-marco) muda o marco — grade
de 1 dia pela escala desenhada (c._E), só com pointer:fine; duplo clique no nome ([data-cr-nome]) renomeia em
linha (o clique simples no nome espera 300 ms pelo duplo); ✎ ([data-cr-editar], também no detalhe do épico)
abre o formulário: nome, Data de início, marco, responsável (usuariosP), status (transições lazy via
/api/transicao), dependências "bloqueado por" (links Blocks entre épicos do projeto, add/rm) e 📐 Alinhar ao
marco os filhos que vencem depois dele; + Nova fase ([data-cr-nova]) cria o épico (tipo de hierarquia 1 do
catálogo) com inicio/venc/respId.crPatchLocal muda a ficha em memória e redesenha; crGrava confirma (POST /api/criar {editar:1, itens, links}); falha → reverte + toast; sucesso → ↩ Desfazer por 10 s (crUndoMostra) e releitura da
ficha com nocache em segundo plano (crReleFicha, debounce 1,5 s). crLinhas (semáforo, gate de paridade com o
servidor) não muda.api/criar.js editaLote): campos novos resumo (1–255), inicio (customfield_10015, '' limpa) e
links:[{acao:'add', de, para, tipo?}|{acao:'rm', id}] (≤ 40; POST/DELETE /rest/api/3/issueLink); inicio
também na criação; a ficha (api/_lib/projetos.js) passa a trazer o id de cada link. Caches atividade:,
venc:, visao:, epicos: são limpos na gravação.cronograma-test.mjs (Playwright, 41 casos — ✎ salvar/undo, renomear, arrastar barra/alça/◆, falha reverte,
dependências add/rm, 📐 alinhar, transições, + Nova fase, sem identidade, celular) contra o servidor-mini.mjs
(FICHA_ACME editável, /api/_edicoes).Pedido do usuário: "ajudar meu time a organizar seu trabalho, usar o Jira Insight como ponto central de planejamento do
dia, incluindo controle da agenda e priorização de tarefas — veja o que o mercado possui para inspiração". A pesquisa
(43 ferramentas: Sunsama, Akiflow, Toggl, Clockify, Reclaim, Motion, Tempo, Todoist, Things, MS To Do, Jira "For you",
Linear…) e as decisões estão em docs/hoje-planejamento-do-dia-2026-10-21.md.
📆 Hoje (public/js/24b-hoje.js, prefixo hj; vistas hoje e hojetime, grupo de abas hoje): KPIs
(capacidade = mpCapDia − reuniões · planejado · apontado · feitos), ✅ lista do dia (ticket, atividade do plano
semanal, reunião ou tarefa livre; ordem manual — arrastar/↑↓ —; os 3 primeiros são o 🎯 foco; estimativa editável; ✓
feito; ⏱ apontar com a hora do bloco; ▶ cronômetro; 📅 agendar; ⋯ amanhã/ligar a ticket/tirar), ⚡ ordenar por
urgência (hjUrgencia: vencidos → vence hoje → 🎯 prioridade do time → em andamento → vence na semana → sem data),
💡 sugestões por fonte (hjSugestoes: ficou de antes, plano da semana, vencidos, vence hoje, prioridades,
andamento, reuniões, recentes) e a 🕘 linha do tempo (eventos do Outlook, blocos com ini, worklogs com hora
(mtsMinSP), "agora", lacunas ≥ 30 min do expediente 08–18h; arrastar item → bloco na grade de 15 min; ⚠ conflito;
🗓 deep link de compose do Outlook — o painel não escreve na agenda).hjRitualPlanejar (ficou de antes → sugestões → estimativas × capacidade com "amanhã" item a item) e
hjRitualFechar (não feitos → amanhã/deixar/descartar; feitos sem apontamento → worklogs em lote; nota). O item
movido fica marcado movido:'AAAA-MM-DD' na origem e nasce com orig:'ontem' no destino (hjProxUtil = próximo
dia com capacidade).hjTimerInicia/hjTimerParaModal/hjTimerPinta): localStorage jirainsight_timer_v1; pílula
#hj-timer em todas as telas; ■ → worklog com hora real; ▶ também na 🔍 ficha do ticket (data-hj-timer-k).POST /api/config?dia=1 (planejamentoDia em api/config.js): ler {de, ate} (≤ 14 dias) · salvar {d, dia, base} (trava otimista pelo updated_at; diaLimpa valida ≤ 60 itens, chave, hora, est ≤ 24 h) · time {d}
(só gestor). Uma linha dia_<accountId>_<AAAA-MM-DD> por pessoa e dia em jirainsight_config (como semanal_*) —
nunca em cfg. No navegador: estado.hoje.dias[d] (undefined = não lido · null = sem plano · objeto com );
lê [d−7, d] sem atropelar dia em memória; grava uma por vez (/).A pedido do usuário, o 🗺️ Roadmap passou a existir também como base de dados no Notion (processo TI-14-024, filha do TI-14 como os demais), com as dimensões que a tela do app não tem: onda, categoria da melhoria, área, telas, tamanho (P/M/G/GG), esforço estimado em tokens e em horas, impacto, prioridade (fórmula Impacto × Tamanho), valor para, origem, pré-requisitos/desbloqueia, dependência externa, decisão pendente, referência de mercado, datas e links (Jira, PR). Regras:
ROADMAP tem um id:'R###' estável (scripts/check-entrega.mjs reprova
id ausente ou repetido). A tela do app mostra o id ao lado de cada item e linka a base..github/workflows/roadmap-notion.yml roda scripts/roadmap-notion.mjs a cada push na main que mexa no
05 — upsert por id; precisa dos secrets NOTION_TOKEN (integração interna com acesso à base) e
NOTION_ROADMAP_DB, e sem eles só avisa; (3) à mão, node scripts/roadmap-notion.mjs --dry mostra o plano.
O script tem teste contra um Notion falso (criação, atualização, "fora do roadmap", descrição longa
fatiada em blocos de 2.000, paginação, dry-run, id duplicado).A árvore (ARV_NOS em public/js/14-planejar.js, vista ondecrio e embutida na 🏠 Início) foi reescrita a partir
dos projetos ativos do Jira. A 1ª pergunta é para quem é o trabalho:
| Caminho | Projeto |
|---|---|
| Cliente › AMS direto (atendimento / reunião / administrativo) | projeto AMS do cliente (categoria DAMS, sem o DCP), tipo não faturável na reunião e no administrativo |
| Cliente › AMS por consultoria parceira | DCP (fixo) + campo "AMS | Consultoria > Cliente" |
| Cliente › alocado por hora num projeto de parceiro | cards da categoria PEA (Copel, Cast Sumitomo, Convista MRV, Cast Hi-Mix) |
| Cliente › escopo fechado | categoria PEF/DEF: entregável planejado → apontar na história · surgiu agora → subtarefa do responsável · alinhamento → épico de Gestão · nada disso → fale com o GP |
| Cliente › proposta ou oportunidade | PV (fixo) |
| Cliente › o projeto ainda não existe no Jira | ADM (fixo), com o tipo do pedido escolhido pela resposta (Projeto AMS / Dexterity / Terceirizado / Hora Aberta) |
| Dexterity › rotina | IFCEC (finanças, contabilidade, controladoria) ou RDF (demais), tipo pela frequência |
| Dexterity › melhoria / produto | cards IMI / IPA (projeto sem categoria no Jira entra pelo prefixo do nome, ex.: IMECI) |
| Dexterity › estudo | DU (gera material reaproveitável) ou TAD › Pessoas & Cultura |
| Dexterity › pedido pontual | TAD + departamento |
| Ausência | pedido no Odoo (sem ticket) |
As folhas acham os projetos pela categoria (filtro:'cat' + catRe), então projeto novo numa categoria entra
sozinho; chave fixa = soProjeto (string ou função do formulário — a rotina escolhe IFCEC/RDF), excl tira chaves
da sugestão, tipoPorFreq/tipoPorNovo pré-selecionam o tipo. O antigo ramo "Projeto SAP" (que procurava "SAP" no
nome e a categoria "Delivery", inexistentes) virou o ramo de escopo fechado. Teste: arvore-test.mjs (Playwright,
40 casos, com a lista real de projetos de 26/10/2026 no lugar do /api/projetos).
A agenda do Outlook (POST /api/reunioes {agenda:1}) é lida desde a segunda-feira da semana passada (dia de São
Paulo) até 14 dias à frente — antes começava ontem. Na 📅 Agenda, os dias antes de ontem ficam no bloco recolhido
🕗 Dias anteriores (com a contagem de eventos e de "sem ticket"); no 🎫 Controle, as pendentes antigas entram em
🕗 Já aconteceram, e a conferência "achei no Jira" vai em lotes de 8 (limite por pedido do servidor), das mais recentes,
até 48 reuniões.
Botão 🔗 transferir em cada ticket da 🛠 Gestão, do ⏱ Apontar e da ficha 🔍 (e o "Transferir →" da 🗂 Reuniões).
Leva TODO o histórico de um ticket para outro — existente ou novo, no mesmo projeto ou em outro — e só então
exclui o original. Tela: public/js/23b-transferir.js (prefixo tft); servidor: api/_lib/transferir.js, sub-rota
POST /api/reunioes {transf} (sem função nova), tudo com o token da pessoa.
Etapa (transf) | O que faz |
|---|---|
ler | inventário da origem: comentários, anexos, vínculos, filhos (parent = KEY), worklogs, changelog |
conferir | ficha do destino (tipo, nível, projeto) para as checagens da tela |
destino | cria o ticket novo (com responsável/vencimento/rótulos quando o projeto aceita) ou comenta o cabeçalho no existente |
comentarios | copia cada comentário com "autor em data" no cabeçalho, mantendo a restrição; mídia da origem vira texto |
anexos | baixa da origem e reenvia ao destino (até 25 MB cada) |
vinculos | refaz cada vínculo no destino, com o mesmo lado |
filhos | muda o pai dos filhos para o destino |
historico | um comentário com a linha do tempo (status, responsável, resolução…) |
horas | recria SÓ os worklogs da própria pessoa (mesmo started e duração) |
excluir | apaga a origem — recusa se ela ainda tiver filhos ou se o destino não responder |
As horas dos colegas vão por convite (POST /api/apontar {convidar}, um por pessoa e dia, aviso no Teams só no
1º de cada pessoa): a API do Jira só cria worklog no nome de quem tem o token. A tela guarda o que cada etapa já fez; se
algo falha, nada é excluído e "↻ Tentar de novo" refaz só o que faltou. Bloqueios mostrados antes: filho que não
cabe no destino (subtarefa não muda de projeto; o nível do destino tem que ser o da origem), anexo acima do limite,
origem com mais de 300 comentários, 1000 apontamentos ou 100 filhos.
Repetir é seguro: tudo o que vai ao destino leva um marcador com o id da origem e cada etapa lê o destino antes de
escrever, então um timeout ou uma aba recarregada não duplica nada (o ticket criado é achado pela propriedade
tft.origem). O excluir só apaga se a origem não ganhou nada desde o inventário (a tela manda os ids que viu) e se
a conta de serviço não enxerga comentário/apontamento restrito que a pessoa não vê. Também vão os links externos e a
prioridade; componentes, versões e campos personalizados aparecem na tela como "não vão". Testes:
agsem/transferir-servidor-test.mjs (32 casos, Jira falso) e transferir-test.mjs (Playwright, 48 casos).
Cada chamado tem 🔀 mover para reclassificá-lo para outro projeto sem sair do
apontamento. O seletor mostra só os destinos compatíveis (que têm um tipo com o
mesmo nome do chamado). Reusa o bulk move do Jira via POST /api/reunioes
({ alvo, itens:[{id, tipo}] }, aceitando a chave do issue), com o token da pessoa.
Os chamados ficam agrupados por projeto (cabeçalho com código + nome). Em cada
chamado dá para 💬 comentar e transferir o responsável (para outra pessoa,
para mim ou remover), além de apontar horas, mover status e reagendar. Comentário e
transferência usam o token da própria pessoa via POST /api/transicao nos modos
{ comentar:true, issue, texto } e { atribuir:true, issue, accountId } (accountId
vazio = sem responsável).
Na aba Apontar dá para reagendar a data de vencimento de qualquer chamado
(clique no 📅 do vencimento) com atalhos Hoje / Amanhã / Próxima semana, data
livre ou remover data. Marque Incluir sem vencimento para também trazer os
chamados abertos sem data e programá-los. A escrita usa o token da própria
pessoa (mesmo modelo do apontamento) via POST /api/transicao no modo
{ reagendar:true, issue, duedate }; a listagem com sem-data usa
GET /api/vencimentos?incluirSemVenc=1.
O endpoint /api/teams monta um cartão (KPIs: apontamento geral, horas faltando,
pessoas em dia/atrasadas + ranking de quem mais precisa apontar, com selos
🟢 Em dia / 🟡 Atrasado / 🔴 Crítico) referente ao último dia útil e o envia ao canal.
TEAMS_WEBHOOK_URL na Vercel..github/workflows/apontamento-teams.yml (cron 0 11 * * 1-5 = 08:00
America/São_Paulo, seg–sex; o endpoint pula feriados). Para proteger o endpoint, defina
CRON_SECRET na Vercel e o mesmo valor como secret do repositório no GitHub
(Settings → Secrets and variables → Actions). Opcional: secret TEAMS_CRON_URL para
apontar a um domínio próprio.dry=1 para só montar o cartão sem enviar), ou abra /api/teams?dry=1.O botão 🐞 do canto (também ⋯ Mais › 🐞 Reportar, Ctrl+K e a ❓ Ajuda) abre um formulário curto —
🐞 Bug / 💡 Melhoria / ❓ Dúvida, título, descrição e, no bug, passos e esperado — e um relato vira dois
registros ligados: o ticket no Jira (JIRA_FEEDBACK_PROJECT, padrão JI, labels hub-feedback + bug/melhoria)
e o issue no GitHub (GITHUB_ISSUES_REPO), com o link do ticket no issue e um remote link do issue no ticket.
Dúvida vai só ao GitHub. O ticket é criado com o token da pessoa (ela é a relatora); sem permissão no projeto,
a conta de serviço cria e o nome de quem relatou vai na descrição. A identidade é conferida no Jira antes de tudo
(meusConfereIdentidade). Se a pessoa deixar marcado, vai junto o contexto técnico — a tela (caminho do menu),
o endereço (parâmetros com cara de segredo são cortados no servidor), a versão, o navegador e os últimos 8 erros
do navegador — e ela vê exatamente o que vai antes de enviar; nada do conteúdo da tela. Rascunho e "Seus últimos
relatos" ficam só no navegador (jirainsight_reporte_rasc_v1, jirainsight_reportes_v1); o envio entra no 🗒 Histórico
de ações. Sub-rota b.reportar do api/criar.js (sem função serverless nova).
O acordo é anunciar cada melhoria publicada no canal Avisos Gerais. Isso era manual
(alguém montava o curl e precisava lembrar), e por isso entregas ficaram sem anúncio.
Desde 2026-09-27 é automático:
main que mexa em public/js/05-novidades-roadmap.js
(.github/workflows/novidades-teams.yml).scripts/aviso-novidades.mjs compara o arquivo do commit atual com o do commit
anterior (git show HEAD~1:…) — sem guardar estado em lugar nenhum. Se NOV_VER
não mudou (correção, refactor, mexida só no roadmap), ele sai em silêncio./api/teams responde 200 com {enviado:false}
quando o webhook não está configurado — o script trata como erro e o workflow fica
vermelho, em vez de o aviso sumir sem ninguém notar. E se o commit de base não
estiver no clone (checkout raso), ele recusa rodar: tratar isso como "não havia
nada antes" publicaria o histórico inteiro no canal. Por isso o workflow usa
fetch-depth: 0.dry=1 (só mostra o cartão). O campo base recebe um commit de comparação —
é como se anuncia uma leva antiga que ficou para trás (ex.: base=5a5e163).TEAMS_AVISOS_WEBHOOK_URL na Vercel (já configurada) e, se você
definir CRON_SECRET na Vercel, o mesmo valor como secret do repositório.⚠️ Hoje o
/api/teamsaceita POST sem autenticação, porqueCRON_SECRETnão está definida na Vercel — quem descobrir a URL publica no canal mencionando o time inteiro. O código já trata a env: basta defini-la na Vercel e criar o mesmo secret no GitHub (os workflows já mandam oBearerquando ele existe).
api/ (funções) e public/ (estático) automaticamente.Rodar localmente: npm i -g vercel e depois vercel dev (crie um .env a partir de .env.example).
No projeto do Vercel: Settings → Deployment Protection → Password Protection → defina a senha. Todos do time acessam o painel com a mesma senha. (Recurso de planos pagos do Vercel; é a forma recomendada por não exigir autenticação caseira.)
public/fonts/ e
descomente os @font-face no topo do index.html. Sem eles, usa-se um fallback neutro.public/logo.svg — uma pétala única
replicada com rotações de 90° (3 grafite #404040 + 1 cerceta #17A398 no topo-direita).
O cabeçalho usa <img src="/logo.svg"> e o mesmo arquivo serve de favicon; para trocar a
marca, basta substituir o logo.svg.NAO_FATURAVEL_REGEX. Se preferir usar o atributo de billable do próprio
Clockwork, dá para evoluir lendo as worklog properties (requer mapear a chave do atributo)./rest/api/3/issue/{key}/changelog._lib/util.js por Vercel KV.09b-meu-tempo.js | ⏳ Como estou gastando meu tempo? (histórico de apontamentos com comentários, gráficos, qualidade dos dados, colaboração, análise por IA) |
10-timesheet.js · 11-ranking.js · 12-tickets.js | 🔎 Timesheet · 🏆 Ranking · 🎫 Tickets |
12b-busca-tickets.js | 🎫 Busca de tickets (tecla /, Ctrl+J, botão #btn-tkb na barra, #btn-mais-tkb em ⋯ Mais e as linhas 🎫 da paleta Ctrl+K): sobreposição projeto → ticket. O projeto sai da memória do app (projNomes/projetosUnidos + garanteProjetos; recentes no localStorage); os tickets abertos do projeto vêm de GET /api/reunioes?abertos=PROJ (sem endpoint novo — a Vercel está no limite de 12), com cache de 3 min no navegador (_tkbCache, ↻ força), filtro em memória (tkbFiltra/tkbOrdena, teto de TKB_MAX linhas) e teclado (↑ ↓ · Enter · Backspace volta). A chave (RDF-123) abre qualquer ticket, aberto ou concluído; 🌐 Todos os projetos aparece só quando estado.analytics.dados já está carregado. A ficha e as ações são as de 20-gestao.js (abreModalTicket(k, volta) ganhou o ↩ Voltar à busca) |
13-apontar.js | ⏱ Apontar (identidade, minhas horas, transições, reagendar, convites, reunião em grupo) |
13b-apontar-pai.js | 🧩 Hierarquia no ⏱ Apontar: caminho do pai no cartão (até 4 níveis), agrupar por épico/pai, seletor de pai por nível e vínculo em lote |
14-planejar.js | 📝 Planejar em lote, 🌳 colar estrutura, árvore "onde crio", CSV, templates |
14b-meus-tickets.js | 🧾 Preciso criar meus tickets: relato em texto livre por projeto → achar/criar o ticket → horas → status → confirmar tudo (POST /api/criar {meus:1}) |
14c-chamadas.js | 📞 Ligações (Call tracking): recebe do detector do Mac a ligação encerrada (#ct= no fragmento), guarda no navegador e o modal cria o ticket e/ou aponta no horário real, casando com a reunião da 📅 Agenda |
15-alertas.js | 🚨 Central de Alertas (reprogramar, atribuir, log de ações) |
16-contratos-ams-receita.js | 💼 Contratos & Valores, 🛠️ AMS & Governança, 💰 Receita |
16b-parcerias.js | 🤝 Contratos de parceria (cfg.parcerias): consultoria, modalidade (horas abertas · AMS · demanda fechada), validade e período de aviso, valor-hora, fechamento do período de faturamento e dia da nota ajustáveis mês a mês (ajustes), conta de recebimento, calendário dos próximos 12 períodos, histórico; 📂 pasta do contrato no SharePoint + 📄 documentos (c.pasta, c.docs[] — só links http(s), validados por pcUrl; todos veem e abrem, gestores cadastram) e 👥 equipe e alocação (c.equipe[] = recursos com uma LISTA de trechos {de, ate, modo, v} — % do dia · h/dia útil · h/mês · h no total: pcHorasRegra/pcPlanejamento somam pelos dias úteis e devolvem horas e valor por período de faturamento); ✎ ajuste manual da previsão (c.prev[ym]={h,v}, pcPrev/pcPrevSet: gestores digitam as horas e/ou o valor de um período por cima do cálculo — o calculado continua visível em horasAuto/valorAuto e o campo vazio volta ao automático); helpers pcPeriodos/pcStatus usados pela Rentabilidade; 🗂 projetos do contrato (2026-10-05: c.frentes[] — ⏱ alocação ou 🛠️ AMS sob demanda, projeto do Jira, código no parceiro, padrões da planilha, valor-hora e objeto de resultado próprios, estimativa mensal do AMS; cada trecho aponta a sua frente em regra.fr), a ✔ apuração por período e projeto (c.apur[ym][frente], pcFrPeriodo = apurado > parcial + previsto do resto > previsto), pcPlanejamentoFrentes, o prazo de recebimento (pcPrazo) e a retenção na fonte |
16c-parcerias-odoo.js | 🧾 Contrato de parceria ↔ Odoo Vendas (2026-09-20): o contrato é o dono da ordem de venda (c.odoo) — um item por período de faturamento + um por ➕ hora extra (c.extras), cada um com o rateio por 🎯 objeto de resultado (contas analíticas → analytic_distribution; c.rateio padrão, c.rateios[ym] por período). ↻ Sincronizar lê o faturado/pago (?acao=odoo-venda-status) e compara a previsão com a ordem (?acao=odoo-contrato com dry:1); a escrita só depois do "Aplicar". Remover o contrato cancela a ordem (?acao=odoo-contrato-cancela). Faixa 🧾 Próximas notas (pcNotasPendentes, também no 📆 Fechamento do mês) e catálogo de produtos/objetos (?acao=odoo-catalogo). Com 🗂 projetos (2026-10-05), um item por projeto e período (p:AAAA-MM:frente, objeto de resultado do projeto) — período que a ordem já tem no formato antigo continua um item só (pcOdooLegado) — e 📤 levar ao Odoo a partir de (c.odooDesde, pcOdooDesde/pcNoOdoo: sem ordem, o padrão é o período atual) |
16e-parcerias-importa.js | 📥 Importar apontamentos do parceiro (2026-10-05): lê a planilha de apontamentos aprovados exportada do sistema do parceiro — .xlsx sem dependência (índice do zip à mão + DecompressionStream('deflate-raw') + XML de sharedStrings/estilos/1ª planilha), CSV ou o colado do Excel —, acha o cabeçalho (Data, Horas, Descrição Projeto, Cliente, Classe de valor, Situação, Recurso, Id), agrupa por projeto do parceiro, casa com os 🗂 projetos (padrões/código PTC) ou propõe criar (tipo e projeto do Jira sugeridos), soma por período de faturamento do contrato (parcial quando o arquivo não cobre o período), sugere a alocação pelo ritmo de h/dia de cada mês (pcImpTrechos) e a estimativa do AMS; nada é gravado antes do "Aplicar" (pcImpAplica) |
16f-parcerias-caixa.js | 💰 Fluxo de caixa das parcerias (2026-10-05): cada nota (projeto × período + horas extras) vira uma entrada — vencimento da fatura no Odoo ou nota + prazo de recebimento; situação recebido/faturado/previsto/atrasado/antes do Odoo; bruto e líquido (retenção) — numa grade de 12 meses por contrato (▸ por projeto), por mês de recebimento ou de nota. Só leitura (pcCaixa/pcCaixaEntradas) |
16d-portal-cliente.js | 🌐 Portal do cliente (2026-09-28, ?v=portal, Dexterity Entrega; chip 🌐 na espinha do projeto): o gerente escolhe o projeto e, em 5 seções (?v=portal&pproj=KEY&psec=…), publica o portal, configura (apresentação, canal do Teams, pasta do SharePoint, calendário do projeto com "salvar e testar", links, time, épicos ocultos, blocos), cura o conteúdo (pendências · riscos · FAQ · reuniões numa tabela editável por tipo; decisões da 🎯 Prioridades ganham o interruptor "visível ao cliente"), gerencia os acessos (pcliBlocoHTML) e pré-visualiza como o cliente (iframe /portal.html?preview=KEY + postMessage com a identidade do gestor). Tudo pelas rotas ?pcli=admin-* (gestor validado no Jira); a tabela é a fonte da verdade (atualizado_por), publicar/despublicar vai ao 🗒 Histórico |
17-metricas-tipo.js | 📈 Métricas por tipo de projeto (DEA/PEA, DEF/PEF, AMS, ARQ, IMI, IPA, ITPR) + ⚙️ Perfis (nível e departamento por pessoa) |
17b-rentabilidade.js | 💹 Rentabilidade de projetos: plano por tipo — nas horas abertas, a linha Horas vendidas do planner nasce da carga vendida mas aceita o número digitado por mês (p.vendMan['AAAA-MM'], 2026-09-21: entra em rpVendPorMes, a fonte única, e por isso vale para receita, eficiência, saldo, períodos de faturamento/Odoo, realizado × previsto e gráficos; o calculado fica ao lado e o ↺ devolve o plano) — (horas abertas: 🤝 contrato de parceria, início/fim, h/dia, valor-hora → receita mensal e por período de faturamento do contrato, receita realizada, ordem de venda no Odoo com um item por período via /api/resumo?acao=odoo-venda e sincronização do faturado/pago via ?acao=odoo-venda-status; escopo fechado: valor + marcos de faturamento ligados a épicos; interno: orçamento consumido pela alocação), planner pessoa × mês com alocação por regra (% do dia, h/dia, h/mês, total entre datas — dias úteis sem feriados, al.regra), cenários, simulador, realizado e histórico do plano (p.hist: quem/quando/o quê) — tudo em cfg.rentab |
_arquivado-planejamento-alocacao.js | 🧮 Planejamento macro e 👥 Alocação — arquivado (telas em reformulação desde 2026-08-17): fica no repositório, não carrega; arquivos que começam com _ ficam fora da lista e do gate. O topo do arquivo diz como reativar |
18-controladoria-relatorios.js | 🏦 Controladoria (por categoria; bloco 🧭 Todas as categorias; 📁 Resultado do projeto — ctResultado/ctResultadoHTML com estado.ctrl.destaque: custo por nível e por pessoa, vendido × realizado, plano × realizado, evolução), 📚 Central de Relatórios (matriz O/R por tipo; "Abrir no app" leva o contexto do relatório — estado.relCtx, faixa #rel-ctx — que restringe a tela-alvo aos projetos dos tipos O/R via relProjOk; catálogo por código R: anl/rm em REL_CAT, relCodigosDe, chips [data-rel-vis] e selos [data-rc-abrir-rel]) |
18b-relatorio-semanal.js | 📊 Relatório semanal de sexta (R28, ?v=semanal, aba do grupo 📚 Central): lê as fotos semanais (GET /api/config?semanal=1&n=8[&semana=], com identidade e papel decidido no servidor) e mostra esta semana, a anterior, o Δ, a média de 4 e a série de 8 — KPIs do time (planejado · orçado/capacidade · realizado · execução · aderência · consumo · qualidade da informação), tabelas por pessoa e por projeto (chips 🧭 Pendências e 📁 ficha), texto da IA e "📋 copiar resumo". realFechado (a semana anterior recontada seg→dom na sexta seguinte) aparece com selo 🔒 |
19-mencoes-inbox-analytics.js | 💬 Menções, 📥 Inbox, 📈 Analytics de governança |
20-gestao.js | 🧰 Gestão de tickets (filtros salvos, ações em massa, ficha do ticket) |
20b-gestao-projeto.js | 🧭 Pendências do projeto (R29, ?v=gp, aba do grupo 🛠 Tickets do time; chip 🧭 na espinha do projeto; escopo na URL em gproj para não disputar com o filtro global proj): a visão do gerente — meus projetos (cfg.projGerentes, editado na ficha do 📁 projeto com o líder do Jira como sugestão; meusProjetos() é leitor), N dias sem atualização, blocos por pessoa com o pior caso primeiro (⏰ vencidos · 🧊 parados · ⏳ vencem em 7 dias · 📝 cadastro incompleto · ⏱ sem apontamento · 📅 reuniões vencidas · 📊 plano × orçado × realizado da foto) e as ações de cobrança: 📋 copiar a mensagem da pessoa (com o link filtrado), 💬 abrir o chat do Teams, 📣 comentar no Jira em lote (token da própria pessoa, modelo "Cobrar atualização"), 📥 aviso no Inbox (um por remetente+pessoa), 🛠 abrir na Gestão. Dados: /api/vencimentos?analytics=1&dias=N&projetos=A,B (chaves validadas e entre aspas; nenhuma válida → 400) |
21-criacao-rapida-convidar.js | 🎫 Criar ticket por linguagem natural/voz, 📨 Convidar para apontar |
22-rateio.js · 23-transformar.js | ➗ Rateio · 🔀 Transformar chamado em atividade |
23b-transferir.js | 🔗 Transferir ticket: o histórico inteiro (comentários, anexos, vínculos, filhos, status, horas) para outro ticket, e o original é excluído |
24-reunioes-vincular-reclassificar.js | 🔗 Vincular reuniões a AMS, 🔁 Reclassificar |
24b-hoje.js | 📆 Hoje — lista do dia, linha do tempo, rituais 🌅/🌙, ⏱ cronômetro (pílula em todas as telas), 👥 Dia do time, card da Início |
25-ajuda-guias.js | ❓ Ajuda, tour, 🧭 guias interativos |
25b-reportar.js | 🐞 Reportar bug ou melhoria (ticket no Jira + issue no GitHub, POST /api/criar {reportar:1}) |
26-navegacao.js | render() (dispatcher), modal, vaiPara, menus, NAVCAT (catálogo de telas com área), aplicaLente (perfil), ABAS (grupos de abas das fases 2–3: Inbox, Apontar, Meu Planejamento, Criar ticket, Tickets do time, Reuniões, Projetos, Horas do time, Planejamento do time, Central, 📑 Contratos, 🛡 Apuração de contratos, 📊 Visão Geral, 📚 Central de Relatórios — renderAbas pinta #abas sem mexer nas telas; fase 4: memória abaLembra/abaUltima — o menu volta à última aba do grupo — e contadores ABAS_N), favoritos, paleta Ctrl+K |
27-exportacao.js · 28-config-metas.js | ⬇️ CSV/PDF · ⚙️ Configurações, metas & ausências |
29-url-topo.js | estado na URL (links compartilháveis), recarrega(), gaveta e menus do topo |
30-eventos-boot.js | listeners delegados (#conteudo, #modal-body, document), PWA, tema, marca animada e a inicialização — carrega por último |
31-uso.js | 📊 Uso do painel — o coletor e a tela. O coletor conta abertura por troca de tela (gancho de uma linha no render()) e tempo num relógio de 15s que só anda com a aba visível e com interação nos últimos 5 min (usoPulso); o acumulado fica no localStorage por `dia |
CLOCKWORK_ESCRITA | não | 1 ativa o modo direto: ao convidar, tenta criar o worklog dos convidados via API do Clockwork (autor explícito); se a API recusar, o convite segue pendente (fallback automático) |
GITHUB_TOKEN | não | token do GitHub (issues:write) usado pelo 🐞 Reportar bug ou melhoria para abrir o issue no GitHub (POST /api/criar com reportar:1; o feedback:true antigo continua aceito). Sem ele, o ticket do Jira sai e a tela avisa que o GitHub falta configurar |
JIRA_FEEDBACK_PROJECT | não | projeto do Jira que recebe os relatos do 🐞 Reportar (padrão JI). Bug → tipo Bug; Melhoria → tipo Melhoria/Improvement se existir, senão História com a label melhoria |
GITHUB_ISSUES_REPO | não | repositório destino dos issues de feedback no formato owner/repo (padrão dfg-dexterity/jirainsight) |
ANTHROPIC_API_KEY | não | chave da API da Anthropic (Claude) — habilita o resumo das atividades por IA na aba Resumo (/api/resumo). Sem ela, o card explica como configurar |
ANTHROPIC_MODELO | não | modelo usado no resumo por IA (padrão claude-opus-4-8) |
ODOO_URL | não | base do Odoo (ex.: https://suaempresa.odoo.com) — habilita o botão 🌴 Folga na aba Resumo (POST /api/resumo?acao=folga) |
ODOO_DB | não | nome do banco (database) do Odoo |
ODOO_LOGIN | não | login da conta de serviço que cria as folgas (precisa de direitos de Time Off) |
ODOO_API_KEY | não | API Key do Odoo (Preferências → Conta → Segurança) ou a senha da conta de serviço |
ODOO_FOLGA_TIPO_ID | não | id (número) ou nome do Tipo de ausência (hr.leave.type) usado nas folgas, ex.: Compensação de horas |
ODOO_PRODUTO_SERVICO | não | id ou nome do produto de serviço usado nos itens das ordens de venda (as "horas de consultoria"); sem ele, vale o produto escolhido em 🧾 Faturamentos › ⚙ Produtos ou o 1º produto de serviço à venda |
ODOO_PRODUTO_EXTRA | não | id ou nome do produto das horas extras nos itens da ordem; sem ele, o das horas de consultoria |
JIRA_FOLGA_PROJETO | não | projeto Jira onde a automação folga aprovada → ticket cria os tickets (id ou key; padrão 10442 = TAD) |
JIRA_FOLGA_TIPO | não | id do tipo de issue dos tickets de folga (padrão 10009 = Tarefa) |
JIRA_FOLGA_DEPT_FIELD / JIRA_FOLGA_DEPT_VALUE | não | campo e valor do Departamento Dexterity nos tickets de folga (padrões customfield_10924 / RH - Pessoas & Cultura). Para não preencher o campo, defina JIRA_FOLGA_DEPT_FIELD=off |
JIRA_FOLGA_HORAS_DIA | não | sobrepõe a jornada (horas/dia) do worklog; vazio = usa a jornada do Odoo (resource.calendar.hours_per_day, fallback 8) |
FOLGA_SYNC_LOOKBACK_DIAS | não | janela (dias) de folgas aprovadas recentes a varrer por execução (padrão 14) |
FOLGA_SYNC_MAX | não | máximo de folgas processadas por execução (padrão 200) |
JIRA_FOLGA_MAP | não | JSON {"email":"accountId"} para exceções de mapeamento Odoo→Jira quando o e-mail não bate |
CLOCKWORK_ESCRITA | não | 1 habilita o worklog automático (no nome da pessoa) via API do Clockwork — usado pela folga-sync e pelos convites |
CRON_SECRET | não | protege os endpoints de cron (/api/teams e /api/resumo?acao=folga-sync) com Authorization: Bearer |
agMarcaTicketcall-trackingagenda-outlook/api/apontarhoraHH:MMparcial.keychamada-criarchamada-apontarcurl -fsSL https://jirainsight.vercel.app/call-tracking/instalar.sh | bash
— baixa o .swift, compila no Mac (precisa das Ferramentas de Linha de Comando do Xcode; o script abre o
instalador da Apple se faltarem), roda o autoteste, monta ~/Applications/Dexterity Call Tracking.app com
assinatura local e liga o LaunchAgent br.com.dexterityit.calltracking. Desinstalar: …/desinstalar.sh | bash
(-s -- --tudo apaga também as preferências)..github/workflows/call-tracking-mac.yml compila para Apple Silicon e Intel (alvo macOS 12) no macOS 14 e
15 (e com o Xcode mais antigo da máquina), reprova símbolo do SDK 14 fora do autoteste, roda o autoteste (que
confere os seletores de 4 letras contra o SDK) e o diagnóstico do Core Audio, o instalador em modo CI e o pop-up de
demonstração (o PNG sai em base64 no log). Teste do painel: chamadas-test (Playwright, 85 casos, inclusive
celular e tablet).01-nucleo.js): duas gravações no mesmo
clique (ex.: agMarcaTicket + logAcao) saíam com a mesma rev; a 2ª dava conflito contra a 1ª e a mescla
descartava a mudança dela. Agora salvaCfgRemota manda uma de cada vez (a que chega com outra no ar sai depois,
com o cfg atual e a rev nova), a base da mescla é a cópia do que foi enviado e, no conflito, o remoto puro
(cfgAdotaRemoto) — não o resultado mesclado, que o servidor ainda não tem.amsChamadosFat, 2026-10-06 — os tipos não faturáveis ficam de fora,
como no PDF). Os campos são configuráveis por env (JIRA_CF_*);revhjGarante(d)hjSalvarsalvandoPdeNovoplanGate(dia) antes e registraExtraDia depois (hjGravaWorklog); o lote do 🌙
grava a config uma vez; a busca 🎫 ganhou o modo escolher (abreBuscaTickets(proj, aoEscolher)); a 🏠 Início tem o
card 📆 Seu dia (hjCardInicio) com ✓ nos 3 do foco e as rotas hoje/hoje-planejar/hoje-fechar.hjCriarModal(d, {ids|texto, proj}) → hjCriarPinta → hjCriarExecuta.
Modal com uma linha por ticket (_hjCr.linhas[{rid, itemId, resumo, desc, p, tipoId, estTxt, erro, key}]: projeto e
tipo por linha — hjTiposDe/hjTipoPadrao leem estado.planejar.projetos, carregados por hjProjetosCriacao do mesmo
/api/projetos do 📝 Criar ticket), vencimento e responsável (eu / sem) para todos, 📋 colar várias (título ; descrição),
↵ = próxima linha, Ctrl+↵ = criar. Entradas: 📝 Criar ticket na linha de adicionar (o texto digitado vira a 1ª linha),
⋯ › 📝 Criar o ticket deste item (tarefa livre) e "📝 Criar tickets dos N itens livres" (em massa). O lote vai ao
mesmo POST /api/criar {itens} (validação e token da pessoa iguais ao 📝 Criar ticket; estimativa no formato do
Jira por hjEstJira). Cada criado entra no dia com orig:'criado' — ou vira a chave do item livre de onde saiu (mesmo
item, nada duplica) — e o dia grava uma vez; logAcao('hoje-criar') por ticket + salvaCfg() uma vez. Linha que o
Jira recusou fica com o erro e ↻ só reenvia as que falharam (key marca o criado). Antes de mandar: título,
projeto com tipo criável, estimativa, vencimento e o teto de 60 itens do dia (um ticket criado sem lugar no dia seria
pior que recusar). Fechou o modal com a criação em voo? O que o Jira criou entra no dia do mesmo jeito.h.sv[d],
timer e "sujo" por dia; hjSalvar devolve true só com o servidor confirmando); mover para amanhã / 🌅 / 🌙 gravam o
DESTINO primeiro (hjLevaPara) e só então marcam movido — conflito/falha não perde item; estimativa limitada a
24 h no cliente (hjEstClamp) e 60 itens (HJ_MAX_ITENS); reuniões da agenda fora do "Planejado"
(hjEhReuniaoContada) e sem conflito consigo mesmas; _logsP solta em qualquer desfecho; 👥 Dia do time com folga de
30 s na falha (403 só pelo ↻); hjGravaWorklog com try/catch (rede caída não prende o modal); ritual com finally e
mensagem preservada no redesenho (r.fb); ⏱ num item livre segue para o apontamento; ⏱ da lacuna só põe o item no dia
depois de gravar (cand); cronômetro preso ao accountId; ↻ e releitura ao voltar à aba (visibilitychange, só
sem edição pendente); Esc limpa _hjAp/_hjTp/ritual (e não fecha durante a gravação); foco devolvido depois do
redesenho (hjFocoSel/hjFocoVolta); pílula abaixo dos modais (z-index 45); cores do tema escuro; alvos de toque;
on_conflict=id na criação concorrente e updated_at=is.null no PATCH sem rev. Lição: ↑↓ aparecendo em
:focus-within deslocava a linha no mousedown e o clique caía fora do alvo — por isso é :has(:focus-visible)..then(renderHoje) numa promessa já resolvida (a janela do Apontar já cobria o dia) virava
laço de microtarefas e travava a página — a leitura das horas é guardada por estado.hoje._logsP e só redesenha
quando passou a cobrir o dia; dia no futuro nunca é pedido.hoje-test.mjs (Playwright, 79 casos: adicionar por chave/texto/busca, estimativa, ordem, urgência, feito,
agendar/conflito/arrastar/lacuna/Outlook, apontar com hora, 🚧 trava, cronômetro (F5, parar, trocar), rituais com
rollover e lote, conflito entre abas, 👥 time × não gestor, celular) contra a fixture ?dia=1 do servidor-mini.mjs.