A seção "Agenda/Horários de atendimento" do modal era uma 3ª fonte de horário que a
Secretária NÃO lia (ela agenda por clinicas_horarios + dentistas_horarios). Removida:
- Modal cuida só de contato (endereço + números + janelas de envio). Para dentista,
mostra o STATUS da jornada real e um botão que abre "Horários de Funcionamento"
(HorariosConfig, aba dentistas) por cima; ao fechar, recarrega.
- Completude do DENTISTA passa a exigir a jornada REAL configurada (dentistas_horarios),
não mais a agenda do modal. GET recomputa `completo` ao vivo + devolve jornada_configurada.
- Backend: comunicacaoCompleto(p, ehDentista, jornadaOk) + helper jornadaConfigurada.
Verificado: dentista com jornada → completo; query bate com dentistas_horarios real.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve o contato do dono/responsável p/ decisões da clínica: por nome (pessoa da
lista de contatos — cobre o dono cujo número está na conta de dentista) ou por papel
(dono/responsável). Mesma forma do /dentista-contato (contato_agora, bloqueado_por_janela,
proxima_janela[_ts]). Verificado: por nome resolve o número compartilhado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Opção (b) — mesma pessoa/número servindo a papéis diferentes, roteando por situação:
- GET /api/comunicacao/numero-lookup: ao digitar um número, avisa se ele já é de outra
conta/papel da clínica (ex.: número na conta dentista aparece ao editar a de dono).
Modal mostra banner "pode compartilhar — descreva as situações; a Secretária roteia".
- Bridge GET /contatos: agenda de contatos internos (nome, papel, situações de cada
número, janela atual) p/ a Secretária rotear por assunto. Não expõe o número cru.
Verificado E2E: lookup como dono acha o nº na conta dentista; /contatos lista papel+situações.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O gate de comunicação interna usava useRef sem importá-lo, quebrando o render
inteiro do App (ReferenceError → tela branca).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Gate progressivo: modal ganha "Lembrar mais tarde" e reaparece a cada login;
a partir da 4ª aparição vira bloqueio até preencher (contador em localStorage
por clínica; ref evita contagem dupla no StrictMode).
- Agenda passa a ser exigida/exibida só para DENTISTA (quem atende). Backend:
comunicacaoCompleto(p, ehDentista) via vinculos.role; GET devolve `role`.
Não-dentista (dono/funcionário) completa só com endereço + números.
Verificado: dono completa sem agenda; dentista exige agenda; role no GET ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Perfil sem endereço próprio → GET /api/comunicacao sugere o endereço já cadastrado
da clínica (só confirmar, não redigitar) + flag endereco_da_clinica. Modal mostra
aviso "puxamos o endereço da clínica — é só conferir". Resolve a fricção do dono,
cuja clínica já tem endereço.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/dentista-contato passa a devolver proxima_janela_ts (ISO do início da próxima janela)
além do label — o motor usa como fire_at do envio diferido.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/dentista-contato passa a ler pessoa_comunicacao (via dentistas.usuario_id):
- Com perfil: só devolve número se a hora atual cair numa janela permitida
(contato_agora); senão bloqueado_por_janela=true + proxima_janela ("sábado às 08:00").
- Sem perfil: back-compat — usa telefone legado e não bloqueia.
Helpers contatoPermitidoAgora/proximaJanela (TZ do container = America/Sao_Paulo).
Verificado E2E: fora da janela -> bloqueado; dentro -> libera nº; sem perfil -> ok.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Botão "Comunicação interna" em cada membro (gestores) abre o mesmo modal full-screen
para preencher/editar o perfil de contato daquele usuário (usuarioId = m.id).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Base da "comunicação interna": cada não-paciente (dentista/funcionário) cadastra
endereço, MÚLTIPLOS números de WhatsApp e a disponibilidade de agenda.
Backend:
- Tabela pessoa_comunicacao (usuario_id+clinica_id): endereco/numeros/agenda JSONB + completo.
- GET/PUT /api/comunicacao/:usuarioId ('me' = usuário atual). Autoriza self OU gestor
(dono/admin). Recalcula `completo` no PUT (regra: endereço + ≥1 número com descrição
e ≥1 janela de horário + agenda decidida; se aberta, exige ≥1 janela).
Frontend:
- ComunicacaoInternaModal (full-screen): Endereço (com ViaCEP), Números (cada um com
descrição + situações + janelas de horário permitido p/ a Secretária enviar msg),
Agenda (abrir/fechar + horários de atendimento). Editor de janelas reutilizável.
- Gate no App.tsx: todo não-paciente com clínica ativa cujo perfil está incompleto vê
o modal FORÇADO em todas as telas (espelha o gate de troca de senha) até completar.
Verificado E2E: PUT incompleto->completo=false; PUT completo->true; GET reflete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Novos endpoints server-to-server (auth x-nw-agenda-secret), presos ao clinica_id
e ao telefone do contato (posse verificada — impede mexer na consulta de terceiros):
- GET /consultas — consultas do telefone/família (futuras por padrão; incluir_passadas)
- POST /confirmar — marca presença (status=confirmado)
- POST /cancelar — cancela e libera o horário (status=cancelado + motivo em observacoes)
- POST /remarcar — move para novo slot com anti-overbooking (findConflict exclui a própria)
Verificado E2E: GET /consultas -> 200 com dentista/procedimento/status.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend:
- Nova coluna salas.contrato (TEXT, migration ADD COLUMN IF NOT EXISTS).
- INSERT/UPDATE de sala aceitam `contrato`; marketplace SELECT devolve s.contrato
(minhas salas já via s.*).
Frontend (SalasPlugin):
- CONTRATO_MODELO: modelo padrão cuja cláusula 2 deixa TODOS os procedimentos sob
inteira responsabilidade do locatário; o dono edita por sala.
- SalaForm ganha textarea "Contrato de locação" pré-preenchida com o modelo +
botão "Restaurar modelo".
- Passo Pagamento: botão "Ler contrato de locação" abre modal com o texto
(contrato da sala, ou o modelo) + checkbox de aceite. "Solicitar Reserva" só
habilita após aceitar; o aceite é registrado nas observações da reserva.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Antes o modelo Dia só permitia um intervalo consecutivo (início + N dias).
Agora permite escolher dias avulsos (ex.: 1 e 5, 3 e 20):
- Clique no calendário alterna (marca/desmarca) cada dia — estado diasMulti[].
- Card mostra chips dos dias selecionados (removíveis; dia ocupado fica âmbar) +
dica "cada dia vira uma reserva (08h–18h)".
- Total = valor_diaria × nº de dias; resumo lista os dias.
- handleReserve envia uma reserva por dia; reporta quantas foram criadas e
quantas falharam (ex.: já ocupado).
- Travas de navegação/botão passam a usar temSelecao (Dia não usa f.inicio).
- Semana/Mês seguem com stepper de quantidade; Hora/Período inalterados.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Calendário (passo 2):
- qtd vira "quantidade" genérica por modelo: Hora=horas (1–12), Dia=dias (1–30),
Semana=semanas (1–8), Mês=meses (1–12). computeRange estende o fim conforme a
quantidade; total = valor × quantidade (Turno segue unitário).
- O intervalo escolhido (diária/semanal/mensal) é realçado no calendário e some
ao trocar de modelo (qtd volta a 1).
- Controles (Início/Duração/Período/Quantidade) que ficavam escondidos abaixo do
calendário agora vivem num card destacado, com cabeçalho do dia/intervalo
selecionado e auto-scroll (scrollIntoView) ao escolher o dia.
- Resumo ganha linha "Quantidade" e a nota do total é genérica (N un × valor).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Seletor de modelo (Hora/Período/Dia/Semana/Mês) sai do passo Calendário e
passa a ficar no passo Dados — o usuário escolhe o modelo primeiro, depois
o calendário/horário reagem à escolha.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rótulos dos modelos: Turno→Período, Diária→Dia, Semanal→Semana, Mensal→Mês
(ids inalterados). Label do controle "Turno" → "Período".
- Calendário cortado no wizard: as células aspect-square deixavam a grade alta
demais p/ a área do slide; troca p/ altura fixa h-9 — a grade fica compacta e
aparece completa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Modal de reservar sala reorganizado num wizard (formato slide, translateX) com
stepper clicável p/ voltar:
1) Dados — reservar como (pessoal/unidade) + profissional + observações.
2) Calendário — modelo (cartões c/ preço) + calendário mensal + horário/turno.
3) Resumo — resumo detalhado (sala, modelo, período, total) + histórico de
reservas da sala.
4) Pagamento — total, forma de pagamento (PIX/Dinheiro/Transferência/Cartão) e
como funciona; a forma escolhida vai nas observações do pedido.
Navegação Voltar/Próximo (Próximo bloqueado no calendário até escolher o dia).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Substitui os dois datetime-local crus por um calendário mensal profissional
(navegação de mês, dia selecionável, dias passados desabilitados, dias ocupados
com marcador, destaque de intervalo p/ semanal/mensal). Modelo de locação vira
grade de cartões com o preço de cada (Hora/Turno/Diária/Semanal/Mensal, só os que
a sala oferece). Controles conforme o modelo: hora (início + duração), turno
(manhã/tarde/noite); diária/semanal/mensal calculam o período. Resumo do período
+ ocupados do dia selecionado. início/fim derivados do calendário (backend igual).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
As duas rotas renderizam o mesmo SalasPlugin com prop `view` diferente; o `tab`
vinha do useState (só no mount) e não reagia à troca de `view`. Agora um efeito
reseta a aba ativa quando `view` muda.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fonte única para os campos de filtro (inputNew/labelNew) e um componente
SearchInput padrão (ícone + input arredondado, encaminha ref/props). Profissionais
e Salas passam a importar do módulo (dedupe). Aplica SearchInput na busca do
Orçamentos como 1ª adoção. Buscas que são autocomplete com dropdown (Pacientes,
Relatórios) ficam para conversão cuidadosa caso a caso (não quebrar o dropdown).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Área de filtros de /alugar-salas restilizada igual ao marketplace de
profissionais: card com busca no topo, filtros recolhíveis (Ocultar/Mostrar),
slider de raio com badge KM, barra de ações (Limpar + Buscar) e busca por
texto (nome/cidade/bairro) sobre o resultado.
- Fix: o marketplace listava as PRÓPRIAS salas (e as da unidade ativa) — agora
exclui owner_usuario_id do usuário e clinica_id da unidade ativa. Não se aluga
a própria sala.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cria components/SegmentedTabs.tsx (controle segmentado padrão, responsivo, com
cor de destaque + ícone + badge) e converte as barras de aba que usavam sublinhado
ou pills soltos: Comissões, Glosas, Lab Clientes, Prótese (OS + cotações),
Contratos, Profissionais (marketplace) e HorariosConfig. Telas que já usavam o
visual (Gestão de Equipe, Notificações, RX, Salas) ficam como referência.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Abas viram segmented control: no mobile ocupam a largura toda e distribuem igual;
no desktop encolhem ao conteúdo (some quando há só 1 aba).
- "Cadastrar Sala" ganha toolbar (contagem à esquerda, botão à direita no desktop,
largura cheia no mobile) em vez de botão solto.
- Filtros do marketplace: grid 2/3/6 colunas (distribui uniforme) e BUSCAR full-width
no mobile.
- Cards de sala: 3 colunas em telas largas (xl).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O menuItems do Sidebar filtra por um allowlist de ids p/ o superadmin, e
'superadmin-secretaria' não estava incluído — por isso a página de motivos não
aparecia no menu. Adicionado ao allowlist.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Botão global no /wa-secretaria agora abre modal com textarea de motivo (obrigatório
p/ desativar) + histórico, em vez de um confirm. Página super admin "Secretária IA"
passa a listar os motivos por ESCOPO: "Todos os chats" (global) e "Uma sessão",
mesclando os dois logs do motor (/secretaria/power/log + /session-power/log).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complementa o fix anterior: no mount, loadChats podia rodar com o activeInstanceId
persistido (stale) antes de loadInstances validar, deixando os chats na tela mesmo
após o reset. Agora o efeito faz setChats([]) quando não há instância ativa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O /wa-inbox persistia activeInstanceId + inboxCache:v1:* no localStorage e o logout
não limpava — então o próximo login (ex.: superadmin) herdava a instância e via os
chats antigos de outra sessão. Agora: clearInboxLocalState() no logout, na expiração
de sessão e na troca de workspace; e loadInstances reseta activeInstanceId se ele não
estiver na lista de instâncias acessíveis (guarda defensiva).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove colunas Número e Quem (telefone/pessoa da clínica) da tabela — a página é
contexto GERAL para melhorar a IA, não para ver dados de uma clínica. Ajusta o texto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nova aba no super admin (/superadmin/secretaria): tabela com quem ligou/desligou a
Secretária de cada número, quando e o MOTIVO do desligamento. Endpoint
/api/superadmin/secretaria-desligamentos (superadminGuard) busca o log global no
motor (resolve um e-mail de dono pareado; log é global). Item no Sidebar + rotas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Botão Power na thin sidebar do /wa-inbox age SÓ na sessão/número aberto (verde=on,
vermelho pulsante=off). Abre modal com textarea de motivo (obrigatório p/ desligar),
histórico de quem ligou/desligou e aviso de rate-limit (1x/hora). secSessionPowerApi
+ SessaoSecretariaPower.tsx.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grade de 30min com rótulo só na hora (slotDuration 30min + slotLabelInterval 1h);
a linha de :30 fica tracejada/fraca (fc-timegrid-slot-minor), distinta da linha
cheia da hora — como na agenda do Google.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
As ações da coluna de controles no desktop eram só uma fileira de ícones sem
rótulo (difícil saber o que era cada um). Agora o desktop herda a estrutura do
mobile: botões empilhados com ícone + nome (Atividade da equipe, Pedidos pendentes,
Configurações, Meus Horários, etc.), badges reposicionados junto ao ícone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/api/dashboard/stats devolvia a agenda inteira da clínica (nomes de pacientes de
todos os dentistas) + faturamento/despesas/growth no payload para QUALQUER membro,
inclusive dentista. Pior: cache por clínica (dashboard:stats:<clinica>) servia o
mesmo JSON a todos os papéis. Agora: dentista vê só a própria agenda; financeiro
só p/ admin/donoclinica/donoconsultorio/funcionario; cache por escopo (papel/dentista).
Verificado: dentista recém-cadastrado (murilo) → agenda vazia + financeiro 0; dono → tudo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Os papéis (sec_numbers) são resolvidos no motor pela conta (x-nw-email). Um
usuário que só existe no scoreodonto (ex.: recepcionista) não tem conta no motor,
então buscar os papéis com o e-mail DELE retorna "conta não encontrada" → todos os
números liberados apareciam como "Sem papel definido". Agora o papel de cada sessão
liberada é buscado com o e-mail do DONO da sessão (cache por e-mail). Verificado:
recep passa a ver o número como role=clinic ("Clínica / Recepção · Rui Cesar").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GET /encaixe/analisar: ocupação do horário desejado (quem está nele) + horário
livre mais cedo (flex-inclusivo) p/ oferecer/encaixar.
- POST /encaixe/mover: remarca um agendamento (o A aceitou adiantar), com checagem
de conflito no destino (findConflict ganha excludeId p/ ignorar o próprio).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
O index.js (já no main) importa createAccountsRoute/provisionNewwhatsAccount do
proxy.js, mas esses exports nunca foram commitados — só existiam no working tree.
Isso quebrava o boot do backend na imagem de produção (SyntaxError: proxy.js does
not provide an export named 'createAccountsRoute') → 502. Commita proxy.js (exports
createAccountsRoute/provisionNewwhatsAccount/operatorSignature/createWsTunnel) e
server.js (usa operatorSignature + parser 12mb em /api/nw/v1), deixando o main
consistente com o backend que já roda em dev.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fecha furos de identificação relatados:
- Telefone compartilhado na família: nova pergunta telefone_compartilhado +
cautelas IDENTIDADE/TELEFONE COMPARTILHADO — a IA nunca assume quem fala pelo
número (ex.: cadastrado no filho, mas quem fala é a mãe; ou pergunta do marido)
e confirma nome + para quem é antes de agendar; se não bater, encaminha ao humano.
- Profissional não cadastrado: cautela para citações de dentista fora da lista
(ex.: prótese com Dr. de fora) — encaminha ao humano e pede no resumo que o
dono cadastre o profissional.
- Pendência de config quando telefone_compartilhado=Sim (completar cadastro dos familiares).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Satélite da Secretária IA:
- /contexto (dentistas+especialidades+situações+cautelas) e /duvida (encaminhar
ao humano sem agendar); agenda_pedidos ganha tipo=duvida + resumo.
- Situações da clínica: tabela agenda_situacoes + config (dono) + aba na UI.
- Checklist do dono (checklist-defs.js + agenda_checklist): perguntas Sim/Não
conferidas contra o banco (dentistas/pacientes) → pendências de config +
cautelas p/ a IA não chutar.
- Área de PENDÊNCIAS DO DONO (PendenciasDono.tsx): engloba config + fila
operacional (horários/dúvidas); botão+badge no /wa-secretaria (só o dono).
- Fila humana (PedidosPendentes): cards de dúvida + resolver; /resolver.
- Botão DESLIGAR global da Secretária no ThinNav (secPowerApi, visível a todos).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Espaçamento/duração padrão do slot passa de 30 para 60 min (1h entre atendimentos).
- /slots aceita ?periodo=manha|tarde (a Secretária pergunta o período antes de oferecer
e busca só aquele período).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Duplicar horário para todos os dias (botão na grade).
- Janela do dentista com DOIS limites: "IA agenda livre" [inicio,fim] e "aceito
confirmar também" (mais cedo/mais tarde) — colunas hora_inicio_flex/hora_fim_flex.
- Zona cinza: quando o cliente pede um horário fora da janela livre, a Secretária IA
NÃO agenda — registra um pedido (POST /pedido) e diz "vou confirmar e retorno".
- Fila da secretária humana: tabela agenda_pedidos + endpoints (listar/confirmar/
recusar); tela PedidosPendentes (confirmar → vira agendamento real, com escolha do
dentista); botão com BADGE de contagem na Agenda (poll a cada 30 min) para staff.
Testado em dev: dentista configura flex; IA registra pedido de 8h30 e responde "vou
verificar com a secretária"; humana confirma → agendamento criado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- TZ=America/Sao_Paulo no backend (compose): a agenda deixa de rodar em UTC —
horários/slots passam a ser Brasília automaticamente (corrige deslocamento de 3h).
- Acesso do DENTISTA: novo botão "Meus Horários" na agenda do dentista, abrindo
a config direto nos horários/folgas DELE (HorariosConfig com initialTab/soDentista,
restrito ao próprio registro). Antes só admin/donoclinica alcançavam a config.
- Config da clínica: gate passa a incluir donoconsultorio (dono de consultório).
Obs.: AgendaView.tsx também traz trabalho acumulado da agenda que estava no
working tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A — Folga/férias do dentista por DATA: tabela dentistas_folgas; o dentista (ou o
dono) marca dias em que não atende; o /slots pula o dentista nessas datas.
Endpoints /dentista/:id/folgas (GET/POST/DELETE) + UI na aba Dentistas.
B — Salas alugadas: o dentista com sala_reserva aprovada passa a ter horários
oferecidos pela Secretária no quarto (disponibilidade = janela da reserva,
casada pelo usuario_id). /slots ganha passada de salas (slot com sala_id +
local); /book grava agendamentos.sala_id (coluna aditiva). Geração passa a usar
CANDIDATE_CAP e ordena por horário (clínica e salas competem).
Testado em dev: folga pula os dias do dentista; reserva de sala gera slots com o
nome da sala.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Secretária passa a saber quando a clínica está aberta e a NÃO oferecer
horário/agendar em dia fechado (domingo, fora do horário, feriado).
- feriados.js: feriados nacionais BR (fixos + móveis via Páscoa) + resolver
com exceções da clínica.
- agenda-schema.js: tabelas clinicas_horarios (horário do dono) e feriados
(exceções/fechamentos), idempotentes.
- agenda-config.js: CRUD com ownership (horário/feriado da clínica = dono;
horário do dentista = ele mesmo ou o dono), em /api/nw/agenda-config.
- agenda-bridge.js: GET /status (aberto hoje?, motivo, próximo aberto, grade,
feriados) + /slots agora = clínica ∩ dentista − feriado.
- HorariosConfig.tsx + botão no AgendaSettingsModal: telas de configuração
(abas Clínica e Dentistas).
Testado em dev: a Secretária ofereceu "amanhã" ao pedirem "hoje" num domingo,
com os horários configurados da clínica.
Obs.: index.js também traz trabalho acumulado do satélite (proxy de mídia via
Wasabi/storage) que estava no working tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adiciona o seletor "Agente / Cérebro deste número" no NumbersPanel: cada
número de WhatsApp passa a apontar para um agente (sec_numbers.agent_id),
tornando a Secretária, os nós do cérebro e a agenda separados POR SESSÃO.
Mostra o cérebro vinculado em cada card; tipos SecNumber.agent_id e
CalendarSlot.instance_id + param instance_id no calendar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- MessageBubble: carrega <url>.thumb.webp na prévia (fallback ao full via onError)
- mediaUnavailable proativo via flag media_recoverable (evita 400 em mídia legada)
- useNewInboxBridge: mapeia mediaRecoverable -> media_recoverable
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AgendaView: controles migrados do header horizontal para uma coluna lateral
esquerda (AGENDAR, ícones de ação em faixa horizontal de tamanho igual,
presença, legenda de dentistas); calendário em flex-1 ocupando a altura toda
- Mobile revisto: cabeçalho compacto (título + AGENDAR) e calendário flex-1
- Sidebar do sistema recolhível no desktop (App: estado persistido + botão
flutuante para reexibir); toggle fica ao lado do título "AGENDA CLÍNICA"
- Toolbar do FullCalendar compactada (título/botões/cabeçalhos menores)
- Título da agenda responsivo (não gera scroll horizontal na coluna estreita)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- WhatsChatDrawer: busca o chat pelos últimos 8 dígitos (o WhatsApp grava o jid BR
sem o 9º dígito, ex. 556799591687), evitando falso "sem conversa"
- AgendaView: slot usa pacienteNome || pacientenome (backend retorna minúsculo),
corrigindo o card que não exibia o nome do paciente
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Drawer de WhatsApp focado (WhatsChatDrawer) aberto do slot do agendamento, com
dropdown de números do paciente + grupo familiar e fluxo de iniciar conversa
- Auto-provisionamento de conta no motor: eager no cadastro/convite + self-heal no
proxy (404 "conta não encontrada" → provisiona e refaz o request)
- Ownership por sessão (wa_session_authz): dono do workspace (owner_id ou vínculo
donoclinica/donoconsultorio) tem poder total; delega re-scan, excluir sessão,
apagar conversa/mensagem, acesso ao Inbox e à Secretária — por conta
- Enforcement no proxy: guardSessionAction (scan/rescan/delete sessão),
guardAreaAccess (inbox/secretaria), guardInboxDestructive (apagar conversa/msg)
- Endpoints /api/nw/authz (dono gerencia) e /api/nw/access (acesso agregado)
- Assinatura do operador nas mensagens (*Nome*, desambiguação de homônimos → *Rui C.*)
- UI: painel "Gerenciar acesso" com 5 toggles; menu e botões de apagar condicionados
- Proxy de mídia /api/nw/media + re-download sob demanda
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Painel do meio (selecionar agente) passa de 280px para 336px (md:) e, no mobile,
ocupa a tela (flex-1) com o painel principal oculto (hidden md:flex).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>