Changelog

Novidades, melhorias e correções da plataforma Donara.


Nenhuma versão encontrada para "".
v2.9.42 27/08/2026

Segurança

  • [CRÍTICO] phpMyAdmin com auto-login sem senha corrigido: docker-compose.yml definia PMA_USER/PMA_PASSWORD no serviço phpmyadmin — isso ativa o modo auth_type=config do phpMyAdmin, que autentica qualquer visitante automaticamente, sem pedir credencial nenhuma. pma.donara.com.br estava publicamente acessível dando acesso total a todos os bancos de tenants para qualquer um que soubesse a URL. Variáveis removidas do compose — o phpMyAdmin volta ao login por cookie, exigindo usuário/senha do MySQL a cada acesso (docker-compose.yml)
  • Senha do MySQL root rotacionada: a senha em produção era literalmente root (hardcoded no docker-compose.yml, nem vinha do .env apesar de existir uma variável MYSQL_ROOT_PASS não utilizada). Gerada uma senha forte, aplicada ao usuário root já em execução via ALTER USER, e o compose corrigido para referenciar ${MYSQL_ROOT_PASS} do .env (antes inerte) — passa a valer também em qualquer reprovisionamento futuro do volume (docker-compose.yml, .env do servidor)
  • Credenciais SMTP centralizadas no .env: landing/saas/secrets.php tinha usuário/senha do SMTP Locaweb hardcoded diretamente no arquivo; passaram a vir de variáveis de ambiente (mesmo padrão já usado para as chaves do Asaas), com fallback apenas para os valores não sensíveis (host/porta/remetente). Nenhuma senha foi alterada nesta mudança — só a forma de armazenar (landing/saas/secrets.php, landing/saas/secrets.php.example, .env.example)
  • Token do GitHub removido do .env local: havia um Personal Access Token comentado dentro do .env (nunca commitado, mas ainda um segredo em texto puro em disco). Removido; recomendado revogá-lo em github.com/settings/tokens caso ainda esteja ativo
  • ASAAS_BASE_URL explícito no .env: antes dependia do fallback implícito do código (sandbox.asaas.com) quando a variável não estava definida no servidor. Ambiente sandbox confirmado como proposital (ainda não migramos para produção no Asaas) — agora está declarado explicitamente em vez de implícito
  • Senha do usuário de banco da aplicação (vouchers_app/DB_PASS) rotacionada: senha anterior era fraca (Vouchers@1). Gerada uma senha forte, aplicada via ALTER USER e propagada ao .env do servidor; container da aplicação recriado para usar a credencial nova
  • Senha do usuário root da VPS (acesso SSH) rotacionada: aplicada via chpasswd, escolhida pelo próprio responsável pela infraestrutura para permanecer memorizável

Adicionado

  • Banner de consentimento de cookies (LGPD) nas páginas públicas: até agora não havia nenhum aviso ou mecanismo de consentimento de cookies, e a Política de Privacidade/Termos de Uso não mencionavam cookies. Novo componente reutilizável (includes/cookie_consent.inc.php, css/custom_cookie_consent.css, js/customJQuery_cookie_consent.js) incluído nas 5 páginas públicas (landing, cadastro/assinar, simulador, política de privacidade e termos de uso): barra fixa com "Aceitar todos" / "Rejeitar não essenciais" / "Personalizar", modal com categorias Essenciais (sempre ativa) e Analytics/Marketing (hoje sem nenhum script real atrelado — infraestrutura pronta para quando for necessário). Preferência gravada em cookie de primeira parte (donara_cookie_consent, 180 dias) e exposta via window.DonaraConsent para scripts futuros de analytics consultarem antes de carregar. Política de Privacidade ganhou seção "7. Cookies" documentando os cookies essenciais já usados (sessão, CSRF, OAuth Google) e um link para reabrir as preferências; Termos de Uso ganhou seção equivalente com renumeração das seções seguintes (includes/cookie_consent.inc.php, css/custom_cookie_consent.css, js/customJQuery_cookie_consent.js novos; landing/index.php, landing/assinar/index.php, landing/simulador/index.php, privacidade/index.php, termos/index.php)
  • Cookies de sessão do fluxo de cadastro (/assinar) e do painel saas-admin endurecidos: só includes/session.inc.php (app principal) chamava session_set_cookie_params() com Secure/HttpOnly/SameSite=Lax antes de session_start() — as sessões de /assinar (cadastro, OAuth Google) e do saas-admin dependiam dos defaults do PHP. Mesma proteção replicada em todos os pontos de entrada de sessão dessas áreas (landing/assinar/index.php, action.php, oauth_google.php, oauth_callback.php, oauth_login.php, oauth_login_callback.php, oauth_link_confirm.php, ativar.php, landing/saas-admin/auth.php)
  • Backup diário automático (bancos + uploads) com painel de controle no saas-admin: até agora não existia nenhum backup do Donara — o único script de backup já ativo na VPS é de outro projeto (Postgres do "Emilia"). Novo scripts/backup_db.sh roda 1x/dia (3h) e faz dump de todos os bancos MySQL (donara_master + 1 por tenant, descobertos dinamicamente — tenant novo entra sem precisar editar o script), compacta os uploads de template por tenant (voucher/custom/) e os logs por tenant (logs/files/) — nenhum dos dois está versionado em git. Cópia local na VPS com 90 dias de retenção + réplica no Google Drive via rclone. Novo painel /saas-admin/backups.php: saúde da última execução (reaproveita saas_cron_runs, mesmo padrão do monitor de faturamento), status por tenant (nova tabela saas_backup_tenant_status), botão Pausar/Retomar o agendamento e botão Executar agora (nova tabela saas_backup_control) — como o container da aplicação não tem acesso ao Docker do host, "Executar agora" funciona por flag no banco lida por um watcher (scripts/backup_watch.sh, a cada 2min), não por execução direta (scripts/backup_db.sh, scripts/backup_watch.sh, landing/saas-admin/backups.php, landing/saas-admin/action.php, novas tabelas em landing/saas/patch_backup_control.sql)
  • Conta suspensa por inadimplência agora pausa a cobrança na Asaas em vez de deixar acumular: quando o webhook PAYMENT_OVERDUE suspende um tenant, a assinatura na Asaas passa a receber endDate na hora (mesmo mecanismo já usado no cancelamento), e qualquer cobrança futura já criada com antecedência (~9 dias) é excluída — a mensalidade não gera mais ciclos novos enquanto a conta estiver suspensa. Cobranças já vencidas/pendentes não são afetadas, continuam sendo cobradas normalmente. Ao confirmar o último pagamento pendente (PAYMENT_RECEIVED, reativação automática), o endDate é removido e a assinatura volta a gerar ciclos normalmente (landing/saas-webhook/index.php)
  • Conta suspensa agora pode ser cancelada pelo próprio cliente: antes, uma vez suspensa, a única saída era falar com o suporte — mesmo que o cliente só quisesse parar de dever, não voltar a usar. O card "Cancelar assinatura" e o cancelamento (cancelar_assinatura()) agora aceitam status suspended; nesse caso o encerramento é imediato (hoje), não no fim do ciclo, já que uma conta suspensa não tem acesso a preservar. Cancelar não apaga cobranças já emitidas — a Asaas continua tentando cobrar o que já venceu (config/assinatura.php)
  • "Ativar plano pago" agora deixa o cliente escolher o plano: antes a tela sempre ativava o plano que já estava gravado em tenants.tier (o default do cadastro), sem opção de trocar — quem quisesse Pro ou Premium acabava assinando o Starter sem perceber. Agora exibe os 3 planos (Starter/Pro/Premium) lado a lado, nenhum pré-selecionado, com o resumo (mensalidade, taxa variável, primeira cobrança) atualizado dinamicamente conforme a escolha; o plano escolhido é gravado em tenants.tier junto com a criação da assinatura na Asaas (config/ativar_assinatura.php, includes/plans.inc.php)
  • Downgrade de plano agora só entra em vigor no fim do ciclo atual, não na hora: como não há reembolso do valor já pago, aplicar o downgrade imediatamente dava ao cliente o resto do ciclo no plano inferior de graça. Agora o pedido fica agendado (tenants.pending_downgrade_tier + pending_downgrade_effective_at, calculado via getTenantBillingState()) e só é efetivado pelo cron diário (cron_finalize_invoices.php) quando a data chega — a tela de Assinatura mostra um aviso explicando que o plano atual continua válido até lá. Upgrade continua cobrando o pró-rata antes de liberar, como já era (config/assinatura.php, landing/saas/cron_finalize_invoices.php, nova migração landing/saas/patch_pending_downgrade.sql)
  • Saas-Admin reorganizado: sidebar preta, home com cards-filtro, tenant.php em abas laterais, planos editáveis em cards e blacklist reordenada: o painel /saas-admin/ tinha o menu de navegação (<nav>) copiado e colado em 7 arquivos, sem nenhum include compartilhado, e a página de detalhe do tenant havia virado uma rolagem de 11 cards. Novo include landing/saas-admin/_sidebar.php (mesma lógica/dinâmica da sidebar do app principal em includes/navbar.php — colapso, overlay mobile — mas em tom preto/quase-preto para diferenciar visualmente) substitui o nav duplicado nas 8 páginas do painel. Na home (index.php), os cards de status (Ativos, Trial, Pendentes, Suspensos, Cancelados, Erro) agora funcionam como filtro da tabela de tenants, combinando com a busca por texto. Em tenant.php, os 11 cards viraram 8 seções isoladas (Dados Cadastrais, Linha do Tempo, Login Google, Gestão da Conta, Cobrança, Atividade, API, Zona de Perigo) navegáveis por um menu lateral secundário, preservando o link direto #danger já usado pela ação "Excluir tenant" da listagem. Em plans.php, os 3 planos passaram de formulário empilhado para cards lado a lado no mesmo estilo visual dos cards de planos da landing page, com nome/preço/badge/features editáveis in-place (mesmos campos de formulário, sem mudança no backend action.php). Em blacklist.php, o card "Adicionar à blacklist" foi movido para o topo da página (landing/saas-admin/_sidebar.php novo, css/custom_saas_admin.css novo, landing/saas-admin/icons.php, landing/saas-admin/index.php, landing/saas-admin/tenant.php, landing/saas-admin/plans.php, landing/saas-admin/blacklist.php, landing/saas-admin/signup_failures.php, landing/saas-admin/backups.php, landing/saas-admin/presets_editor.php, landing/saas-admin/default_rule_models.php)
  • Planos (Saas-Admin) — taxa e preço não exigem mais digitar a mesma informação duas vezes: os campos "Taxa — voucher valor fixo" e "Taxa — voucher desconto %" tinham um texto livre (descrição completa) e a taxa numérica lado a lado, sem nenhuma relação entre si — qualquer ajuste de taxa exigia editar os dois manualmente, e era fácil deixá-los dessincronizados. Agora cada bloco tem a taxa numérica primeiro e um texto complementar depois (ex: 4.00 + % sobre vouchers de valor fixo); o texto final exibido na landing/assinatura é montado automaticamente concatenando os dois na hora de salvar. O preço mensal também deixou de exigir dois campos separados (parte inteira + centavos) — agora é um único campo decimal (ex: 29.90), convertido internamente para as mesmas colunas já usadas por todas as telas que exibem o preço formatado (landing/saas-admin/plans.php, landing/saas-admin/action.php, nova migração landing/saas/patch_plan_taxa_suffix.sql adicionando saas_plans.taxa_fix_suffix/taxa_pct_suffix)
  • Link discreto para reexecutar o assistente de primeiro acesso, no lugar do banner temporário em Config: o card "[Dev] Testar mini-wizard" na aba Aparência era uma ferramenta temporária de teste que ficou esquecida na interface. Removido; a mesma função (disparar o assistente de senha/dados/tema/template na própria conta, só para Master) passou a ficar acessível por um link discreto no rodapé da página de Ajuda, sem poluir a tela de Configurações (config/index.php, ajuda/index.php, primeiro_acesso/resetar_wizard.php)
  • Botão "Renderizar" em Config > Templates: ao lado de "Salvar Layout", salva o layout atual e abre em nova aba o voucher gerado pelo motor GD real de produção (voucher/index.php), com os mesmos textos de amostra exibidos no editor — mesma ideia do botão equivalente no saas-admin, mas reaproveitando o renderizador real (novo modo de prévia opcional, ativado só com sessão de admin) em vez de duplicar toda a lógica de posicionamento (voucher/index.php, config/index.php, js/customJQuery_config.js)

Corrigido

  • Config — ordem das abas ajustada: sequência agora é Template Horizontal, Template Vertical, Modelos de Regras, Aparência, Dados da Conta, Integrações (antes começava em Aparência) (config/index.php)
  • Config — aba padrão ao abrir a página continuava sendo Aparência: mesmo após reordenar as abas, a página sempre abria em Aparência por padrão (só mudava se o usuário clicasse em outra aba ou viesse de um link com ?tab=). Agora abre direto em Template Horizontal para administradores; nível 0 continua caindo em Aparência, única aba que enxerga (config/index.php)
  • Upload de logotipo do template quebrado ("Erro ao enviar logo"): config/upload_logo.php chamava csrf_validate() sem nunca incluir o arquivo que a define — erro fatal silencioso, mascarado por um error_reporting(0) que suprimia até o registro no log de erros. Corrigido criando includes/csrf.inc.php (helper isolado, sem a dependência de Xajax que includes/auth.inc.php tem no nível principal do arquivo) e trocando error_reporting(0) por ini_set('display_errors', '0'), que mantém erros reais visíveis no log do servidor sem expô-los ao usuário. A mesma falta de proteção CSRF foi corrigida em config/upload_template.php (nunca validava o token, e o JS nunca enviava um) (includes/csrf.inc.php novo, includes/auth.inc.php, config/upload_logo.php, config/upload_template.php, js/customJQuery_config.js)
  • Texto explicativo abaixo da prévia do template atualizado: agora deixa claro que os valores/textos são ilustrativos (editáveis com duplo clique) e que pequenas variações de navegador/resolução podem afetar o posicionamento real, orientando a usar o novo botão "Renderizar" para conferir o resultado final (config/index.php)
  • Templates — "Restaurar padrão" ignorava o modelo selecionado: o botão de restaurar coordenadas em Config > Templates sempre voltava para o layout genérico do sistema (o mesmo usado por vouchers novos sem nenhum modelo aplicado), mesmo com um preset específico (ex.: Modelo 2) selecionado — perdendo o posicionamento próprio daquele modelo. Agora restaura para as coordenadas salvas do preset atualmente ativo, só caindo no padrão genérico quando não há preset selecionado (canvas em branco). O botão "Definir layout atual como padrão" foi removido — ficava redundante e confuso ao lado do comportamento corrigido, já que cada preset já guarda seu próprio layout (config/index.php, js/customJQuery_config.js)
  • Cor do badge "Voucher válido" em Gerência e no formulário de Cadastro não seguia as cores definidas em Config > Aparência: config/index.php salva as cores personalizadas em vouchers_users.user_config (por usuário — mesmo local que o Dashboard já lia corretamente), mas gerencia/index.php (listagem, cards e o modal de estatísticas) e cadastro/index.php (cores do formulário de criação) liam de vouchers_config.colors, uma chave gravada uma única vez no provisionamento do tenant e nunca mais atualizada. Corrigidos os 4 pontos de leitura (2 em gerencia/index.php, 1 em cadastro/index.php, mais o endpoint de atualização de cards após ações) para ler do mesmo lugar que o Dashboard, eliminando a divergência entre telas (gerencia/index.php, cadastro/index.php)
  • Dashboard — barras do gráfico "Emissões por Usuário" desproporcionalmente grossas com poucos usuários: a espessura de cada barra era proporcional à altura total do canvas dividida pelo número de usuários, então com 1 ou 2 usuários a barra ficava enorme. Adicionado limite fixo de espessura (maxBarThickness: 24) e reduzido o incremento de altura por usuário no cálculo dinâmico do canvas, deixando as barras finas e consistentes independente da quantidade de usuários (js/customJQuery_dashboard.js)
  • Logs de auditoria e de acesso registravam o IP interno do Traefik, não o do visitante: toda a stack roda atrás do Traefik (reverse proxy), então $_SERVER['REMOTE_ADDR'] sempre valia o IP do container do Traefik na rede Docker (ex.: 172.24.0.10) — o mesmo em qualquer tenant, sem nenhuma utilidade para auditoria. Nova função client_ip() (includes/client_ip.inc.php) lê X-Forwarded-For e usa o último IP da lista (o que o próprio Traefik anexa ao repassar a requisição — não o primeiro, que um cliente malicioso poderia forjar), com fallback para REMOTE_ADDR se o header não existir (ex.: acesso direto sem proxy). Substituídas todas as 40 ocorrências de $_SERVER['REMOTE_ADDR'] em 18 arquivos (logs de login, criação/edição de voucher, exportações, alterações de config, validação pública, cadastro SaaS, API externa, etc.). Testado em produção via bubba.donara.com.br, confirmando IP público real capturado corretamente (includes/client_ip.inc.php novo; includes/connect.inc.php, landing/saas/db.php incluem o helper; demais arquivos apenas trocam a chamada)
  • Assinatura — botão "Selecionar plano" quebrado durante o Trial: os cards de planos apareciam mesmo sem assinatura criada ainda, e clicar em "Selecionar" tentava alterar uma subscription inexistente na Asaas (erro "Assinatura sem data de início registrada"). Durante o Trial os cards somem — tudo já está liberado nesse período (exceto a API) — e a escolha de plano passa a acontecer só em "Ativar plano pago". O cabeçalho de status também deixou de mostrar um nome de plano (ex.: "Plano Starter") durante o Trial, já que nenhum plano foi de fato escolhido ainda — mostra o título "Trial" no lugar do nome do plano, sem repetir o badge de status ao lado. O botão "Simular custos mensais" também passou a ficar dentro do mesmo bloco condicional dos cards, já que só faz sentido junto com eles (config/assinatura.php)
  • Banner de consentimento de cookies (LGPD) ocupando quase a tela toda no mobile: o <p> do texto usava flex: 1 1 420px (herdado da regra desktop, onde o flex é em linha e 420px vira largura); no mobile, o media query trocava o flex container para coluna sem resetar esse valor — em flex-direction: column, o mesmo 420px passa a valer como altura mínima do parágrafo, esticando um espaço vazio enorme entre o texto e os botões. Resetado para flex: 0 1 auto no mobile, com botões empilhados em largura total e paddings reduzidos (css/custom_cookie_consent.css). O <link> desse CSS nas 5 páginas públicas não tinha cache-busting (?v=), então navegadores mobile continuavam servindo a versão antiga do cache mesmo após o deploy — agora usa filemtime() como as demais folhas de estilo do projeto (landing/index.php, landing/simulador/index.php, landing/assinar/index.php, privacidade/index.php, termos/index.php)
  • "Ativar plano pago" — links "Cancelar"/"Voltar" quebrados: apontavam para config/assinatura (sem .php), rota que não existe nesse projeto (sem rewrite configurado) — voltar do formulário de ativação dava 404. Corrigidos para config/assinatura.php, incluindo os dois header('Location: ...') de redirecionamento (conta já ativa / conta interna) (config/ativar_assinatura.php)
  • Checagem de limite de vouchers no downgrade contava vouchers expirados como ativos: trocar_plano() contava voucher_usado = 0 sem filtrar por validade, inflando a contagem usada contra max_vouchers do plano de destino — um tenant com vouchers vencidos (mas ainda voucher_usado = 0) via um número maior que o real "Ativos" do Dashboard. Corrigido para usar o mesmo critério do KPI do Dashboard (voucher_usado = 0 AND data_validade >= hoje) (config/assinatura.php)
  • Cadastro (/assinar) — texto desatualizado sobre plano padrão do trial: o card lateral dizia "Sem escolha, a conta é mantida no Starter", que não é mais verdade desde que a escolha do plano passou a acontecer em "Ativar plano pago" — removido, junto com os preços fixos (R$29,90/R$79,90/R$149,90) citados no mesmo card, já que os valores podem mudar e já são exibidos nas telas de planos (landing/assinar/index.php)
  • Assinatura — card "Cancelar assinatura" exibido durante o Trial: não existe assinatura de fato nesse período (só é criada quando o cliente ativa um plano pago), então o card não fazia sentido ali. Some da tela durante o Trial; o xajax cancelar_assinatura() também passou a rejeitar chamadas nesse status, por segurança (config/assinatura.php)
  • Landing page — rodapé sem links para Política de Privacidade e Termos de Uso: as duas páginas existem e já são linkadas em outros pontos (fluxo de cadastro, banner de cookies), mas não no rodapé da própria landing. Adicionados ao lado dos links já existentes (Documentação da API, Changelog) (landing/index.php)
  • Customização de template não era restrita por plano: criar novo modelo, enviar imagem de fundo e reposicionar elementos (salvar_preset, salvar_layout, config/upload_template.php) só checavam nível de usuário — qualquer tenant Starter conseguia customizar livremente, apesar da tela de planos anunciar isso como exclusivo do Pro+. Segue o mesmo padrão já usado para a API (saas_plans.api_enabled): nova coluna saas_plans.template_customization_enabled (1 para Pro/Premium, 0 para Starter) checada nas 3 ações que de fato alteram um template, mais o Trial liberado (mesma regra "tudo liberado exceto API"). Na tela, o Starter continua podendo trocar entre os 3 modelos prontos, mas o botão "Novo modelo" fica bloqueado e o card de edição de posição/upload some, com aviso "disponível a partir do plano Pro" (landing/saas/db.php, config/index.php, config/upload_template.php, css/custom_config.css, js/customJQuery_config.js, nova migração landing/saas/patch_template_customization.sql)
  • Selecionar (não só criar/editar) um modelo personalizado herdado de plano superior continuava liberado após downgrade: aplicar_preset() não checava elegibilidade — um tenant que criou modelos no Pro e caiu pro Starter conseguia continuar trocando livremente entre eles na galeria, mesmo sem poder editá-los. Agora modelos não-protegidos (os de fábrica continuam sempre liberados) ficam bloqueados na galeria com cadeado + aviso quando o tenant não é elegível; o template já ativo no momento do downgrade não é revertido automaticamente, continua valendo até o tenant trocar (config/index.php)
  • Assinatura — loop de "Ativar plano agora" para quem já ativou e só está aguardando a confirmação do pagamento: TRIAL_EXPIRED é calculado só por status='trial' + trial_ends_at vencido, sem checar se a assinatura já foi criada na Asaas — e config/ativar_assinatura.php nunca muda tenants.status ao criar a assinatura (só vira active depois, via webhook PAYMENT_RECEIVED, quando o primeiro pagamento é confirmado). Nessa janela (entre ativar e a confirmação do pagamento), a tela de Assinatura repetia o banner "Período de trial encerrado / Ativar plano agora" mesmo já tendo assinatura criada — clicar no botão só devolvia "assinatura já está ativa", sem indicar o que fazer. Agora, quando já existe asaas_subscription_id, o banner troca para "Assinatura criada — aguardando confirmação do pagamento" com o link direto da fatura pendente (config/assinatura.php)

Alterado

  • Faturamentos — ícone de "Vencido" agora é um link direto para a página de pagamento da Asaas: antes o ícone era apenas informativo; agora, quando a fatura tem invoice_url salvo, o ícone vira um link (target="_blank") que abre a cobrança na Asaas para o cliente pagar. getTenantBillingState() passou a trazer invoice_url no histórico de cobranças, que já era gravado em saas_invoices mas não estava sendo selecionado (landing/saas/billing_calc.php, faturamentos/index.php)
  • Assinatura — banner de conta suspensa: texto trocado de "Conta suspensa por inadimplência" para "Não identificamos o pagamento de sua fatura"; a tabela de faturas do banner deixa de listar a fatura do ciclo em aberto que ainda não venceu (só mostra overdue, ou pending já vencida); cor do aviso "Conta suspensa — regularize o pagamento para reativar" trocada de text-warning para text-warning-emphasis (baixo contraste no tema claro); borda vermelha do card "Cancelar assinatura" removida, volta a ser uma borda normal como os demais cards (config/assinatura.php)
v2.9.41 24/08/2026

Segurança

  • [CRÍTICO] Vazamento de dados entre tenants — "modelo padrão do sistema" era a arte real de um cliente: voucher/Voucher_modelo7_horizontal.png e _vertical.png (usados como fallback de template em TODO tenant sem template próprio configurado) eram, por engano, a arte de voucher real da Clínica Flórez — nome, foto, WhatsApp, Instagram e endereço físico reais. Qualquer tenant sem template_h/template_v configurado exibia esses dados nos próprios vouchers. Confirmado ativo em produção: os tenants trial gpclinica e paineisdefestasliberdade estavam servindo essa arte no momento da correção. Substituídos os 4 arquivos (H/V + variantes de impressão) por PNGs em branco nas dimensões corretas; coordenadas padrão ($APP_DEFAULT_LAYOUT_H/_V) ajustadas junto para combinar com fundo branco (texto preto em vez de branco). Backup dos arquivos originais guardado em /root/leaked_template_backup_20260824/ na VPS (voucher/Voucher_modelo7_*.png, includes/app_defaults.inc.php)

Adicionado

  • Presets de Templates (saas-admin) — botão "Ver voucher real (GD)": salva as posições atuais e abre, em nova aba, o voucher renderizado pelo mesmo motor GD (voucher/index.php) usado em produção, com textos de amostra — permite validar o resultado final de cada modelo sem precisar criar um tenant de teste (landing/saas-admin/preset_voucher_preview.php novo, landing/saas-admin/presets_editor.php, landing/saas-admin/presets_editor.js)
  • saas-admin — "Modelos de Regra" padrão: nova tela (/saas-admin/default_rule_models.php) para editar o(s) modelo(s) de texto de regras que todo tenant novo já recebe pronto na tela de Cadastro de Vouchers — equivalente a "Presets de Templates", mas para o texto das pré-definições de regras. Só afeta tenants criados a partir de agora. Provisioner::provision() agora grava esse padrão (customizado ou o hardcoded do sistema, se nunca configurado) em vouchers_config.rule_models na criação do tenant — antes essa chave simplesmente não era escrita, e o tenant dependia do fallback embutido no código (landing/saas-admin/default_rule_models.php, .js novos; landing/saas/db.php, landing/saas/provisioner.php, landing/saas-admin/action.php; novo item de menu em todas as páginas do saas-admin; nova tabela saas_default_rule_models)
  • saas-admin — monitor de saúde do cron de faturamento: o log diário (cron_finalize.log) não tinha timestamp e sempre dizia "OK", mesmo com falhas individuais (capturadas só via error_log, nunca no próprio log). Agora cron_finalize_invoices.php grava cada execução em saas_cron_runs (início, fim, status, tenants processados, contagem e detalhe de erros), e a tela de Tenants do saas-admin mostra um banner com o status da última execução — verde quando OK e recente, vermelho quando terminou com erro, travou sem terminar, ou está há mais de ~30h sem rodar (landing/saas/cron_finalize_invoices.php, landing/saas-admin/index.php, nova tabela saas_cron_runs)
  • Upgrade de plano no meio do período agora cobra pró-rata antes de liberar o plano superior: antes, o upgrade trocava o tier na hora sem cobrança — um cliente podia fazer upgrade, usar o plano cheio o mês inteiro, e voltar pro plano anterior antes de ser cobrado pela diferença. Agora o upgrade gera uma cobrança avulsa na Asaas (diferença entre os planos, proporcional aos dias restantes do ciclo atual) e só troca tenants.tier de fato quando o pagamento é confirmado pelo webhook (PAYMENT_RECEIVED) — até lá o cliente permanece no plano atual, com um aviso na tela de Assinatura e o link de pagamento. Downgrade continua imediato e sem cobrança, como já era. Testado ponta a ponta em produção (sandbox Asaas): cálculo do pró-rata, criação da cobrança, confirmação via webhook e troca de tier — artefatos de teste limpos depois (landing/saas/billing_calc.php, config/assinatura.php, landing/saas-webhook/index.php, novas colunas tenants.pending_upgrade_tier e saas_invoices.type)

Alterado

  • Modal de Estatísticas (Gerência) — reorganizado: "Por tipo", "Por status" e "Por modalidade" agora são 3 cards compactos lado a lado no topo, sem gráfico de barras. "Por mês (emissão)" e "Por mês (validade)" continuam com barra, mas a porcentagem de cada mês agora é sempre em relação ao total geral do filtro atual — antes era relativa ao maior valor da própria série, fazendo o mês de pico sempre aparecer como "100%" (gerencia/index.php, css/custom_consulta.css)
  • Config — aba "Dados da Empresa" renomeada para "Dados da Conta": passa a exibir os dados cadastrais reais do tenant vindos de donara_master.tenants (empresa/pessoa, CPF/CNPJ, responsável, domínio, e-mail e celular da conta com badge de verificado/pendente, data de cadastro), somente leitura por enquanto. O card "Controlador" (nome da empresa/CNPJ editáveis, salvos em vouchers_config.dpo) foi removido — nome e CNPJ agora têm o cadastro em tenants como única fonte, eliminando a duplicação entre as duas origens (config/index.php, js/customJQuery_config.js)
  • Badge de nome da empresa no login: passou a ler tenants.nome (donara_master) diretamente em vez de vouchers_config.dpo.clinica, acompanhando a mudança acima (index.php)
  • $APP_DEFAULT_DPO reduzido a dpo_nome/dpo_emailclinica/cnpj não são mais gerenciados por essa tela (includes/app_defaults.inc.php)
  • Landing page — passo 2 de "Como funciona": "Selecione o template" → "Dados e template", texto atualizado para refletir que esse passo também inclui preencher os dados da empresa (landing/index.php)
  • Config — aba Modelos de Regras virou accordion: cada pré-definição era um card sempre expandido ocupando quase toda a altura da tela, dificultando reordenar por drag-and-drop com muitos modelos. Agora cada uma é um item de accordion colapsado por padrão, mostrando no cabeçalho o ícone de arrastar, o ícone de modelo, "Pré-definição N" e o nome atual (atualizado ao vivo enquanto digita) — o conteúdo (nome + texto da regra) só aparece expandido. Com os cabeçalhos compactos, dá pra ver e reordenar muitas pré-definições na tela ao mesmo tempo (config/index.php, js/customJQuery_config.js, css/custom_config.css)
  • Config — Modelos de Regras: removidos os botões "Restaurar Padrões" e "Definir como Padrão" (e o código morto associado — definir_padrao_modelos(), rule_models_user_default)
  • Config — Template Horizontal/Vertical em mobile: card "Modelos de Template" (galeria) movido para o topo da aba, antes de qualquer aviso. Removido o segundo aviso ("Tela muito pequena") — ficava duplicado com o de touch/tablet em telas < 576px; mantido só um único aviso (config/index.php, css/custom_config.css)

Removido

  • Config — card "Encarregado (DPO)" removido da aba Dados da Conta: os campos dpo_nome/dpo_email nunca alimentaram a Política de Privacidade de fato — essa página (privacidade/index.php) é estática desde que virou genérica para o modelo SaaS, e nunca leu esses campos dinamicamente. Removidos o card, salvar_dpo(), cfg_salvar_dpo() e $APP_DEFAULT_DPO (config/index.php, js/customJQuery_config.js, includes/app_defaults.inc.php, CLAUDE.md)

Corrigido

  • Config — miniaturas de modelo na aba Template Vertical: usavam a mesma caixa larga (110×55, pensada pro horizontal 2080×854), cortando quase toda a imagem vertical. Agora a aba Vertical usa uma proporção própria (80×120, 2:3 — a razão real do template 1134×1701) (config/index.php, css/custom_config.css)
  • Editores de template — imagens de fundo saíam borradas ao salvar com zoom/pan: o Chrome/Edge usa imageSmoothingQuality: 'low' por padrão ao redimensionar uma imagem num canvas — ao compor uma foto grande para caber no template, o resultado saía visivelmente mais borrado do que a resolução de origem permitiria. Ativado imageSmoothingQuality: 'high' explicitamente na composição, nos dois editores (saas-admin e Config do tenant). Presets/templates já salvos continuam com a versão borrada — é preciso reabrir o editor e clicar em Salvar de novo para gerar a versão nítida (landing/saas-admin/presets_editor.js, js/customJQuery_config.js)
  • Landing page e Simulador — fonte "Lato" não era carregada de verdade: custom_landing.css declara font-family: 'Lato', sans-serif (usada no logotipo da navbar e em todo o texto da página), mas nunca havia um @font-face/link do Google Fonts trazendo a fonte de fato — o navegador sempre caía no fallback sans-serif genérico do sistema. Adicionado o link do Google Fonts (pesos 300/400/700/900) em landing/index.php e landing/simulador/index.php
  • saas-admin — excluir um tenant deixava um vínculo Google órfão que travava recadastro: handleDelete() apagava o tenant e seu banco, mas nunca limpava a linha correspondente em saas_google_links (sem FK/cascade nessa tabela). Se a mesma conta Google fosse usada para recriar um tenant com o mesmo slug depois, o INSERT batia na constraint UNIQUE de google_sub e travava com erro 500 não tratado — tanto na criação da conta quanto no botão "Entrar em conta existente", deixando o usuário sem saída em nenhuma das duas pontas. Agora a exclusão de tenant também remove o vínculo Google associado (landing/saas-admin/action.php)
  • Cadastro/login via Google — pontos sem tratamento de erro podiam derrubar a requisição com HTTP 500 silencioso: mapeados todos os INSERT INTO saas_google_links e blocos de consulta ao banco no funil de OAuth que não tinham try/catch (PHP 8.2 lança mysqli_sql_exception por padrão em qualquer erro de banco, inclusive falha de conexão). Criado linkGoogleAccount() (landing/saas/db.php) centralizando a vinculação: se o google_sub já estiver vinculado ao mesmo tenant/usuário (corrida/retry), trata como sucesso; se apontar para outro tenant (conflito real, como o de uma linha órfã), loga em saas_signup_failures — visível no relatório "Falhas de Cadastro" do saas-admin — em vez de estourar um erro 500 cru. Aplicado nos 3 pontos que faziam esse INSERT diretamente (landing/assinar/action.php, oauth_link_confirm.php, oauth_login_callback.php) e envolvidos em try/catch com log os blocos de busca de tenant/usuário em oauth_callback.php, oauth_login_callback.php e action.php que também não tinham proteção
  • Presets de Templates — botão "Ver voucher real (GD)" ignorava textos editados no preview: a edição de texto via duplo clique (_previewTexts) só existia no JS do canvas, nunca chegava no endpoint de renderização GD, que sempre usava os textos hardcoded de $APP_PREVIEW_TEXTS. Agora pe_visualizar_gd() envia os textos atuais do preview (editados ou não) como parâmetros, e preset_voucher_preview.php os usa na renderização (landing/saas-admin/presets_editor.js, landing/saas-admin/preset_voucher_preview.php)
  • Config — accordion de Modelos de Regras: sombra não acompanhava o arredondado da borda (estava no wrapper .accordion, não no .accordion-item que tem o border-radius) — movida para o elemento certo; ícone de arrastar estava grande demais — reduzido; botão "Remover" agora pede confirmação antes de excluir (config/index.php, js/customJQuery_config.js, css/custom_config.css)
  • Config — Template Horizontal/Vertical: "+ Novo modelo" piscava a imagem errada e caía num layout incompatível com fundo branco: o fluxo aplicava primeiro o modelo de fábrica (Voucher_modelo7, com posições/cores pensadas pra aquele fundo — inclusive código em texto branco) e só depois trocava para uma imagem em branco, deixando a tela mostrar rapidamente a arte errada antes de assentar num layout que não fazia sentido no branco. Agora cria e aplica o preset em branco direto, sem passar pelo modelo de fábrica, usando um novo conjunto de coordenadas pensado especificamente para canvas branco (texto preto, posições ajustadas) — $APP_DEFAULT_BLANK_LAYOUT_H/_V (includes/app_defaults.inc.php, config/index.php, js/customJQuery_config.js)
  • Config — excluir o modelo ativo de Template caía no "Padrão" do sistema em vez do próximo modelo do tenant: reforça o problema de segurança acima — ao excluir o preset de template ativo, o sistema aplicava automaticamente o "Padrão" (o fallback genérico). Agora aplica o primeiro modelo restante da própria lista do tenant, só caindo no "Padrão" caso não sobre nenhum (js/customJQuery_config.js)
  • Config — cor do header do accordion de Modelos de Regras não cobria as pontas: a cor só tinha sido aplicada no botão colapsável do meio — o handle de arrastar (esquerda) e o botão de remover (direita) continuavam com o fundo branco padrão do Bootstrap. Cor movida para o header inteiro, com overflow:hidden no item para acompanhar o arredondado da borda em qualquer estado (config/index.php, js/customJQuery_config.js, css/custom_config.css)
  • Config — miniatura de modelo "Padrão" aparecia em branco: não era um carregamento quebrado — a imagem (branca, após a correção do vazamento de dados) simplesmente se misturava ao fundo claro do card, sem nenhuma borda para delimitar a miniatura. Adicionada borda em .cfg-preset-thumb (css/custom_config.css)
  • Config — accordion de Modelos de Regras: desalinhamento vertical no cabeçalho: causado por misturar <button class="accordion-button"> (com padding/line-height próprios) como irmão de <span>s simples (handle, botão remover) num align-items:stretch. Reescrito como um único elemento flex (.modelo-accordion-header) contendo todos os ícones — handle, ícone, título, preview do nome, seta e botão de remover —, alinhados com align-items:center, sem mais o componente .accordion-button do Bootstrap (config/index.php, js/customJQuery_config.js, css/custom_config.css)
  • Config — Template Horizontal/Vertical em telas pequenas: antes, telas menores que 576px ou dispositivos touch bloqueavam a aba inteira, inclusive trocar entre modelos já salvos. Agora só o card de posicionamento (Preview & Coordenadas) fica bloqueado — a galeria de modelos continua acessível para alternar entre os já configurados. O botão "+ Novo modelo" (que exige posicionar do zero) permanece bloqueado nesses tamanhos de tela (config/index.php, css/custom_config.css)
  • Primeiro acesso — passo "Escolha o modelo do voucher" cortava o título/instruções no mobile, sem como rolar até eles: o body centralizava o card verticalmente com align-items: center — quando o conteúdo (título + 3 modelos com miniaturas H/V empilhadas) ficava mais alto que a tela, o transbordo ficava distribuído igualmente pra cima e pra baixo, e a posição de rolagem 0 já caía no meio do card, tornando o topo (justamente o título e as instruções) inalcançável por scroll. Trocado por margin: auto no card em vez de align-items: center no body — centraliza normalmente quando cabe na tela, e "recua" pra 0 (sem cortar nada) quando o conteúdo é mais alto que a viewport (primeiro_acesso/index.php)

UX

  • Config — accordion de Modelos de Regras: cabeçalho de cada pré-definição agora usa a mesma cor dos demais .card-header do sistema (ex.: cards da Gerência), com variante clara para tema dark, em vez do estilo padrão do componente Bootstrap
v2.9.23 23/08/2026

Adicionado

  • Relatório de falhas silenciosas de cadastro/login (saas-admin): tentativas de cadastro ou login via Google que falham antes de existir um tenant (sessão expirada, código ausente, falha na troca de token com o Google, perfil incompleto, login sem vínculo) agora ficam registradas em saas_signup_failures e visíveis na nova tela "Falhas de Cadastro"; e-mails com mais de uma falha são sinalizados como possível cliente perdido (landing/assinar/oauth_callback.php, oauth_login_callback.php, action.php, landing/saas/db.php, novo landing/saas-admin/signup_failures.php)
  • Código de vendedor/afiliado no cadastro: campo opcional no formulário de /assinar/, com pré-preenchimento via link ?ref=CODIGO; gravado em tenants.affiliate_code e editável posteriormente pelo saas-admin (landing/assinar/index.php, action.php, landing/saas-admin/tenant.php, action.php)
  • "Esqueci minha senha" na tela de login: novo link abre um modal onde o usuário informa o e-mail cadastrado; se existir (mensagem sempre genérica, evita enumeração de contas), gera um token de recuperação válido por 1 hora e envia um e-mail com o link via a mesma infraestrutura SMTP do cadastro SaaS (Mailer::sendPasswordReset, novo). Nova página pública recuperar_senha/ valida o token e exige a mesma política de senha do wizard de primeiro acesso (mínimo 8 caracteres, maiúscula, minúscula, número, especial), com checklist ao vivo. Colunas reset_token/reset_token_exp em vouchers_users são criadas sob demanda (auto-migração lazy, mesmo padrão já usado para tema/user_config — sem exigir alteração manual em cada banco de tenant) (index.php, includes/auth.inc.php, js/customJQuery_login.js, landing/saas/mailer.php, novo recuperar_senha/index.php)
  • Primeiro acesso — rede de segurança para o passo de templates: se por qualquer falha no provisionamento a galeria de presets do tenant não tiver nenhum par completo (horizontal + vertical), em vez de cair silenciosamente no template genérico de fábrica do sistema, o wizard agora ativa automaticamente o Modelo 1 direto dos arquivos compartilhados do saas-admin. Só entra em ação nesse cenário de falha — quando a galeria está OK, a escolha continua sendo sempre do cliente, na etapa "templates" (primeiro_acesso/index.php)
  • saas-admin — confirmação ao excluir tenant: botão "Excluir tenant permanentemente" agora pede confirmação explícita antes de submeter, além da exigência já existente de digitar o slug (landing/saas-admin/tenant.php)

Alterado

  • Tenants (saas-admin): clicar em qualquer parte da linha de um tenant agora abre seus detalhes, sem precisar abrir o menu "Ações" (landing/saas-admin/index.php)
  • Primeiro acesso — política de senha explícita e checada ao vivo: o passo de definição de senha agora exige mínimo 8 caracteres, maiúscula, minúscula, número e caractere especial; uma lista abaixo do campo mostra em tempo real quais condições já foram cumpridas (ícone muda a cada tecla pressionada), e o botão de continuar só habilita quando tudo bate e as senhas coincidem — validado também no servidor (primeiro_acesso/index.php)
  • Primeiro acesso — miniaturas de template empilhadas: no passo de escolha de modelo, as prévias horizontal e vertical de cada card agora ficam uma em cima da outra (em vez de lado a lado), e o card do assistente ficou mais largo nesse passo — corrige o vazamento visual das miniaturas para fora do card (primeiro_acesso/index.php)
  • Usuários — indicador de conta Google vinculada: trocado o badge flutuante genérico (bi-google sobre fundo azul escuro, a cavaleiro no card) pelo "G" oficial com gradiente (vermelho→amarelo→verde→azul) do redesign 2025 da Google; o ícone agora substitui o ícone de pessoa no início da primeira linha do card, em vez de flutuar sobre o cabeçalho. Continua clicável para desvincular (usuarios/index.php, novo images/google-g-gradient.svg)
  • Usuários — alinhamento dos 4 ícones do card: login/Google, e-mail, celular e nível agora ficam numa coluna de largura fixa (.vc-row-icon), centralizados dentro dela — antes cada ícone tinha padding próprio e tamanhos diferentes, então o texto de cada linha começava numa posição horizontal ligeiramente diferente (usuarios/index.php, css/custom_usuarios.css)
  • Login — ajustes visuais: link "Esqueci minha senha" centralizado e movido para depois do botão de Login (antes ficava à direita, acima do botão); footer trocado de "Desenvolvido por Gustavo Pontes" para "© ano Donara · Gestão de Vouchers · suporte@donara.com.br" (index.php)
  • Login — label do campo de senha em português: trocado "Password" por "Senha" (index.php)
  • Política de senha unificada em todas as telas: o cadastro tradicional (landing/assinar/ativar.php, "Defina sua senha" após confirmar e-mail) e o modal "Alterar senha" da navbar (usuário já logado trocando a própria senha) agora exigem os mesmos requisitos e mostram o mesmo checklist ao vivo já usados no wizard de primeiro acesso e na recuperação de senha: mínimo 8 caracteres, maiúscula, minúscula, número e caractere especial. O modal da navbar também valida isso no servidor (senha_atende_politica(), novo em includes/auth.inc.php) — antes não exigia nada além de confirmação e senha atual correta (landing/assinar/ativar.php, includes/navbar.php, includes/auth.inc.php)

Corrigido

  • Usuários — ícone do Google não aparecia: o SVG externo (<img src>) carregava mas não pintava nada em alguns navegadores; trocado por SVG inline (embutido direto no HTML, sem request separado), com id de gradiente único por usuário para não colidir quando há vários cards Google na mesma página (usuarios/index.php)
  • Usuários — ícone das 4 linhas do card deslocado para baixo: a coluna de largura fixa (.vc-row-icon) usava inline-flex + vertical-align: middle, que empurrava todos os ícones (Google, e-mail, celular, nível) visivelmente abaixo do texto. Voltou para inline-block + text-align: center (só centraliza horizontalmente, sem sobrescrever o alinhamento vertical padrão que já funcionava) (css/custom_usuarios.css)
  • Presets de Templates (saas-admin) — "Erro ao salvar imagem: Falha no upload": o botão Salvar recompõe a imagem de fundo inteira em PNG sem perdas a cada clique (mesmo sem trocar a imagem), e para fundos fotográficos isso ultrapassava o limite padrão do PHP (upload_max_filesize=2M). Elevado para 20M/25M (docker/php.ini) — exigiu rebuild da imagem e recriação do container, já aplicado em produção
  • Editor de templates — prévia do "Status" descolada da posição real: o texto "* EXPIRADO *" era tratado como imagem no preview (âncora de centro geométrico, igual ao QR code), mas o GD sempre ancora texto pelo topo — resultado: o preview mostrava o status bem mais acima de onde ele realmente sai no voucher gerado. Corrigido nos dois editores (saas-admin e Config do tenant, que compartilham a mesma lógica) tratando "status" como texto, igual ao elemento "código" (landing/saas-admin/presets_editor.js, js/customJQuery_config.js)
  • Editor de templates — posicionamento de texto agora bate pixel a pixel com o voucher gerado: o preview posicionava o texto usando a caixa de linha do CSS (line-height), que sempre deixa um espaço "invisível" acima/abaixo da letra não presente na renderização real (GD) — mais perceptível em fontes pequenas, dava a impressão de que o texto "flutuava" ou ficava alinhado à base do quadro em vez do topo. Trocado por desenho em <canvas>, medindo a tinta real da fonte (TextMetrics.actualBoundingBox*) e replicando exatamente a fórmula do GD (imagettfbbox/imagettftext) — inclusive o algoritmo de quebra de linha da Descrição. Vale para valor, validade, código, descrição e status, nos dois editores (saas-admin e Config do tenant). A edição de texto por duplo clique foi reescrita (era contenteditable no texto; agora abre um campo temporário sobre o canvas) (js/customJQuery_config.js, landing/saas-admin/presets_editor.js, css/custom_config.css)
  • Editor de templates — edição de texto por duplo clique: o campo sobreposto ao canvas (introduzido na v2.9.22) tinha tamanho incorreto e quebrava a Descrição (linhas sobrepostas, ilegível). Trocado por um modal do Bootstrap — resolve tamanho, quebra de linha e fechamento (Esc, clique fora, X) sem lógica própria (js/customJQuery_config.js, landing/saas-admin/presets_editor.js)

Verificado

  • Confirmado que o fluxo normal (presets do saas-admin → seed na galeria de todo tenant novo → escolha obrigatória no primeiro acesso → ativação) já funciona corretamente ponta a ponta — checado com um tenant real recém-criado. O que ainda não foi customizado é só o posicionamento dos elementos sobre a arte (tabela saas_preset_layouts está vazia — os 3 modelos usam a posição padrão do sistema até alguém reposicionar no editor)
v2.9.9 20/08/2026

Alterado

  • Modelo híbrido de cobrança (fixo antecipado + variável em atraso): ao ativar a assinatura (fim do trial ou antecipação por limite de vouchers), a mensalidade fixa passa a ser cobrada imediatamente para o mês seguinte, em vez de esperar o fim do trial — o variável de uso continua sempre um ciclo em atraso. Elimina o atraso de ~2 meses até a primeira cobrança do modelo anterior (config/ativar_assinatura.php)
  • Cancelamento de assinatura — data de término alinhada ao ciclo real pago: cancels_at agora usa o fim do ciclo de cobrança já pago (via getTenantBillingState()), não mais o fim do mês calendário (config/assinatura.php)
  • Cancelamento — remoção de cobranças futuras já pré-criadas pela Asaas: a Asaas gera a cobrança de cada ciclo com ~9 dias de antecedência; ao cancelar dentro dessa janela, qualquer cobrança pendente com vencimento posterior ao fim do acesso pago é ativamente excluída, evitando uma mensalidade indevida (config/assinatura.php, novo método AsaasClient::deletePayment())
  • Blindagem contra cobrar uso anterior à ativação real: nova coluna tenants.asaas_subscription_started_at grava o momento exato da ativação; usada para zerar o variável de qualquer período anterior a esse momento, cobrindo ativação no prazo, antecipada ou tardia (landing/saas-webhook/index.php, landing/saas/billing_calc.php)

Adicionado

  • Fatura final de cancelamento (só variável): no fim do período pago de um tenant cancelado, o cron diário gera uma cobrança avulsa (fora da assinatura) só com o variável de uso do último período; não gera cobrança nenhuma se não houve uso (landing/saas/cron_finalize_invoices.php, novo método AsaasClient::createPayment())
  • Cadastro — mensagem de limite do trial atingido: ao bloquear a criação de um 16º voucher no trial, a mensagem agora apresenta as duas opções explicitamente — cancelar um voucher ativo para abrir espaço, ou ativar a assinatura imediatamente (cadastro/index.php)
v2.9.8 02/08/2026

Alterado

  • API externa restrita ao plano Premium: antes disponível para Pro e Premium, agora exclusiva do Premium (saas_plans.api_enabled desativado para o tier pro)
  • API exige assinatura paga e ativa — trial nunca libera: mesmo um tenant com tier nominal Premium durante o período de trial agora recebe 403 PLAN_NOT_ELIGIBLE; só conta com status=active (ou em carência paga pós-cancelamento) e tier Premium tem acesso. Contas internas (is_internal) continuam isentas dessa checagem (landing/api/bootstrap.php, config/index.php, landing/saas-admin/tenant.php)

Adicionado

  • Changelog público (/changelog): renderiza CHANGELOG.md automaticamente (parser próprio, sem dependência nova), no mesmo padrão visual de /api-docs. Toda entrada futura deste arquivo aparece publicamente sem nenhum passo extra. Linkado no rodapé da landing e na navegação de /api-docs (changelog/index.php)
  • Autoatendimento de chaves de API — aba "Integrações" em Configurações: Administrador e Master do próprio tenant agora geram e revogam suas chaves de API diretamente pelo sistema, sem depender do painel interno da Donara. Mostra um aviso de upgrade com link para a assinatura quando o plano atual não é Pro/Premium. Mesma biblioteca de geração/hash/revogação já usada pelo saas-admin (landing/saas/apikeys.php), que continua disponível como via de suporte para intervenção manual (config/index.php, js/customJQuery_config.js)
  • API externa — busca avançada (GET /api/v1/vouchers): espelha os filtros de consulta_avancada (gerencia/index.php) — comprador, CPF/celular (dígitos normalizados), descrição, paciente, criador, código, observação, intervalos de oid/valor/datas, tipo, modalidade e status (múltiplos valores por vírgula, combinados com AND entre parâmetros e OR dentro do mesmo parâmetro) — mas com paginação real (page/per_page, até 100 por página) em vez do LIMIT 10 fixo ou lazy-load de 20 em 20 da versão web, e tudo via prepared statements. Nunca retorna provisórios, mesma regra dos demais endpoints. Exige escopo read (landing/api/vouchers.php, landing/api/index.php)
  • Campo oid no Objeto Voucher: adicionado a todos os endpoints que retornam vouchers (consulta, busca, uso, cancelamento) como identificador numérico estável, alternativo ao codigo — mudança aditiva, não quebra clientes existentes
  • Documentação pública da API (/api-docs): página autocontida (sem login, no mesmo padrão de privacidade/ e termos/) com referência completa dos 5 endpoints, autenticação, escopos, rate limit, formato de resposta/erro, objeto Voucher e um índice de todos os error.code possíveis. Inclui busca em tempo real e sumário lateral com scroll-spy (mesmo padrão de ajuda/index.php), exemplos em curl com botão de copiar. Linkada no rodapé da landing pública, na Central de Ajuda e no painel de Chaves de API do saas-admin (api-docs/index.php, landing/index.php, ajuda/index.php, landing/saas-admin/tenant.php)
  • API externa — criação de voucher (POST /api/v1/vouchers): espelha cadastro/action.php. Só tipo comercial/campanha (provisório fora do escopo v1 — não tem cod_validacao e não é encontrável pelos demais endpoints da API). Aplica a mesma checagem de cota de vouchers por plano (trial/pago, is_internal sem limite). Gera cod_validacao único por voucher com o mesmo algoritmo de cadastro/action.php. Suporta quantidade (1–100) para criação em lote, retornando um array de códigos gerados. Exige escopo create. Erros: 422 VALIDATION_ERROR/TIPO_NOT_ALLOWED, 403 QUOTA_EXCEEDED (com tmax/tcur em error.details). Audita cada voucher criado em vouchers_log com tipo = "API - Novo Voucher" (landing/api/vouchers.php, landing/api/index.php) — com este endpoint fecha-se o catálogo mínimo planejado para a API externa: consultar, usar, criar e cancelar vouchers
  • API externa — cancelamento de voucher (POST /api/v1/vouchers/{codigo}/cancelar): espelha cancela_voucher (gerencia/index.php), incluindo o novo bloqueio de "já usado". Ordem de validação: existência, expiração (regra anti-abuso já existente — evita o loophole de estender-depois-cancelar), já usado, já cancelado. Exige escopo cancel. Não tem regra de "ownership por usuário" (nível 0 só cancela o que criou) — a API key já é escopada por tenant. Audita em vouchers_log com tipo = "API - Cancelamento de Voucher" (landing/api/vouchers.php, landing/api/index.php)
  • API externa — uso de voucher (POST /api/v1/vouchers/{codigo}/usar): espelha a validação estrita de marcar_usado (valida/index.php) — checa existência, já usado/cancelado, expiração e o vínculo de CPF (fraude) antes de marcar como usado, nessa ordem. Exige escopo use. Body: { nome_paciente, cpf_paciente }. Retorna o voucher atualizado no mesmo formato do endpoint de consulta. Audita em vouchers_log com tipo = "API - Uso Registrado" (landing/api/vouchers.php, landing/api/index.php, landing/api/bootstrap.php)
  • API externa — consulta de voucher (GET /api/v1/vouchers/{codigo}): primeiro endpoint funcional de voucher da API, espelhando a lógica de leitura de valida/index.php. Retorna status (disponivel/usado/cancelado/expirado), tipo, valor, modalidade de desconto, validade, descrição, regras, data de uso e nome do paciente — sem dados do comprador (PII), mesmo comportamento da tela pública de consulta. Provisórios e códigos inexistentes retornam igualmente 404 VOUCHER_NOT_FOUND, para não vazar a existência de vouchers provisórios. Exige escopo read da API key. Consultas (encontradas ou não) são auditadas em vouchers_log com tipo = "API - Consulta Voucher" (landing/api/vouchers.php, landing/api/index.php)
  • Fundação da API externa (/api/v1/...): primeira fase de uma API REST para sistemas externos consultarem, usarem, criarem e cancelarem vouchers, sem depender de sessão/CSRF web. Autenticação por API key (Authorization: Bearer dnr_live_...), com o tenant resolvido a partir da própria chave (não do subdomínio), constant-time hash compare, escopos (read/use/create/cancel) e rate limit por minuto. Feature exclusiva de tenants em plano Pro/Premium (contas internas ignoram a checagem). Nesta fase só existe o health-check GET /api/v1/ping; os endpoints de voucher chegam nas próximas fases (landing/api/bootstrap.php, landing/api/index.php, landing/saas/apikeys.php, .htaccess)
  • Tabelas saas_api_keys e api_rate_counters em donara_master, mais a coluna saas_plans.api_enabled (landing/saas/patch_api_keys.sql)
  • Painel de gerenciamento de chaves de API em saas-admin/tenant.php: gerar (com label e escopos), listar e revogar chaves por tenant, com a chave em texto plano exibida uma única vez no momento da criação (landing/saas-admin/tenant.php, landing/saas-admin/action.php)

Corrigido

  • Lacuna de validação em "cancelar voucher" na tela de Gerência: cancela_voucher (gerencia/index.php) só bloqueava cancelamento de voucher expirado — não verificava se já estava usado ou já cancelado antes do UPDATE, dependendo só da UI esconder o botão nesses casos. Agora valida os dois casos explicitamente no backend, mesma regra usada no endpoint da API
  • Lacuna de validação em "usar voucher" na tela de Gerência: usa_voucher (gerencia/index.php) não revalidava voucher_usado (já usado/cancelado) nem data_validade (expirado) antes do UPDATE — dependia só da UI esconder o botão nesses casos. Uma chamada repetida/forjada contra um voucher já usado, cancelado ou expirado re-gravava data_uso/nome_paciente/cpf_paciente silenciosamente. Agora valida explicitamente no backend, espelhando a mesma regra já usada em marcar_usado (valida/index.php) e no endpoint da API
  • Cota de vouchers/usuários por plano estava silenciosamente desativada: as queries de enforcement referenciavam a coluna inexistente tenants.plan_tier (o nome real é tenants.tier); como o prepare() falhava e o erro era engolido por um catch (Throwable) silencioso, nenhum tenant tinha limite de vouchers ou usuários aplicado, independentemente do plano contratado. Corrigido em todas as 5 ocorrências (cadastro/action.php, usuarios/index.php)

Documentação

  • /api-docs, Central de Ajuda, saas-admin e aba Integrações atualizados para refletir a restrição a Premium + conta ativa
  • /api-docs e a Central de Ajuda atualizadas: a seção "Como obter acesso" não instrui mais contatar o suporte — orienta gerar a chave em Configurações → Integrações
  • /api-docs atualizada com a referência completa do endpoint de busca e o campo oid
v2.8.7 25/07/2026

Corrigido

  • Consultar — leitor de QR Code abria a câmera frontal em alguns aparelhos: facingMode: 'environment' é apenas uma constraint "ideal" — nem todo navegador/dispositivo a honra, podendo cair na câmera padrão (frontal). Trocado para listar as câmeras via Html5Qrcode.getCameras() antes de iniciar e selecionar por ID a que tem label contendo "back"/"traseira"/"rear"; só recorre ao facingMode como fallback se nenhuma câmera com esse label for encontrada (valida/index.php)
  • Card órfão desalinhado à esquerda em telas mobile: quando a quantidade de cards é ímpar, o último item ficava colado à esquerda em vez de centralizado
    • Cadastro — grid "Tipo de Voucher" (.cad-tipo-grid): abaixo de 991.98px força 2 colunas fixas e centraliza o card órfão da última linha (css/custom_cadastro.css)
    • Dashboard — cards KPI de "Situação Geral": justify-content-center adicionado à row (dashboard/index.php)
  • Cache-busting incompleto (2.8.2): a versão anterior só aplicou ?v=filemtime(...) ao custom.css; os CSS específicos de cada página (custom_cadastro.css, custom_dashboard.css, custom_config.css, custom_fonts.css, custom_consulta.css, custom_logs.css, custom_usuarios.css) continuavam sem versionamento e ficavam presos em cache do navegador mesmo após deploy — corrigido em todas as páginas que os referenciam
  • Sidebar mobile cortada pela barra de URL do navegador: .app-sidebar usava height: 100vh, calculado sobre a maior altura possível da viewport; com a barra de endereço visível, o rodapé da sidebar (toggle de tema, usuário logado) ficava fora da área visível. Adicionado height: 100dvh como override progressivo (css/custom.css)
  • Cache-busting em css/custom.css: link do CSS agora inclui ?v=<?php echo filemtime(...) ?> em todas as páginas, forçando o navegador a buscar a versão atualizada a cada deploy sem depender do usuário limpar o cache manualmente
  • Modal "Editar cadastro" (navbar) quebrado em home/, faturamentos/ e config/ativar_assinatura.php: essas páginas incluíam a navbar (e o link "Editar cadastro") mas não tinham o setup completo do Xajax ($xajax = new xajax(), include de auth.inc.php, processRequest()/printJavascript()) nem o script js/customJQuery_geral.js, causando loadPerfilData is not defined e xajax_load_perfil_data is not defined no console ao abrir o modal

Adicionado

  • Consultar — botão "Trocar câmera" no leitor de QR Code: aparece apenas quando o dispositivo tem mais de uma câmera; permite alternar caso a câmera aberta automaticamente (facingMode: 'environment') não seja a correta, sem afetar o comportamento de abertura automática (valida/index.php)

Alterado

  • Consultar — leitor de QR Code abre a câmera direto: trocado Html5QrcodeScanner (UI própria com botão "Request Camera Permission" → seleção de câmera → "Start Scanning") por Html5Qrcode de baixo nível, iniciado com facingMode: 'environment' — abre a câmera traseira (principal) imediatamente ao abrir o modal, restando apenas o prompt nativo de permissão do navegador (inevitável, exigido pelo próprio browser). Fallback para a primeira câmera disponível via Html5Qrcode.getCameras() caso o dispositivo não identifique uma câmera traseira (valida/index.php)
v2.8.0 30/05/2026

Adicionado

  • includes/app_defaults.inc.php expandido como fonte única de verdade para todos os defaults do sistema:
    • $APP_DEFAULT_LAYOUT_H / $APP_DEFAULT_LAYOUT_V — posições, tamanhos e cores padrão de cada elemento do voucher (movidos de config/index.php); voucher/index.php agora faz um único merge ao carregar o layout do banco, eliminando ~50 fallbacks individuais
    • define('APP_DEFAULT_THEME', 'light') — tema padrão centralizado; referenciado em navbar.php (3 ocorrências) e comentado no SQL de auth.inc.php
    • $APP_PREVIEW_TEXTS — textos de amostra do editor de templates; injetados via PHP como _appPreviewTexts na config page e consumidos por CFG_ELEMENTS.sample no JS (com fallback inline)
    • $APP_VOUCHER_STATUS_TEXTS / $APP_VOUCHER_EMPTY_CAMPANHA — textos de status e placeholder de campanha vazia do renderizador GD
    • $APP_DEFAULT_DPO — campos DPO vazios (movidos de config/index.php)
    • $APP_DEFAULT_RULE_MODELS — modelo de regra pré-configurado exibido no primeiro acesso ao sistema; após o tenant salvar modelos pela primeira vez (mesmo que apague tudo), o default nunca mais é aplicado (distinção via presença da chave rule_models no banco)
  • Dashboard JS: fallback colors de CORES, kpiConfig e finConfig alinhados com $APP_DEFAULT_COLORS (eram valores Bootstrap divergentes)
  • Home — grid de cards dinâmico por perfil de usuário: número de colunas calculado automaticamente em PHP ($col_xl_map / $col_xxl_map) conforme total de cards visíveis, eliminando orphans desalinhados
    • xl (≥1200px): máx 4 por linha — nivel 0 → 3+2, nivel 1 → 4+4, nivel 2 → 3+3+3
    • xxl (≥1400px): nivel 2 → 5+4 via classe customizada .col-xxl-5th (20%); min-height: 230px nos cards para manter proporção visual adequada
    • justify-content-center na row — orphans sempre centralizados
    • Extensível: adicionar card futuro requer apenas incrementar $num_cards na condição correspondente

Corrigido

  • Home — card Configurações não aparecia para usuários nivel 0 (estava dentro do bloco if($user_nivel >= 1))
  • Sidebar — submenu do usuário com fonte desproporcional: font-size: 0.82rem e padding alinhados com os demais links da sidebar
  • includes/app_defaults.inc.php: modelo de regra default com quebras de linha no textarea (\n em string de aspas duplas); tema padrão ajustado para light
v2.6.0 29/05/2026

Adicionado

  • Cores por usuário — cada usuário configura sua própria paleta de cores independentemente; preferências salvas na coluna user_config de vouchers_users (auto-migration no carregamento da página de config); dashboard lê as cores pessoais do usuário logado com fallback para os defaults do sistema
  • Config acessível a todos os usuários — link "Configs" movido para fora do bloco admin-only na sidebar; usuários nível 0 acessam a página e veem apenas a aba Aparência; abas Dados da Empresa, Modelos de Regras e Templates (H/V) permanecem restritas a admin/master
  • includes/app_defaults.inc.php — defaults de cores centralizados em arquivo compartilhado ($APP_DEFAULT_COLORS); remove arrays duplicados de config/index.php e dashboard/index.php

Alterado

  • Config → Templates: toggle de visibilidade movido para a 1ª coluna do grid (antes do nome do elemento), associando visualmente o botão ao item correspondente
  • Config → Templates: logotipo — botões "Carregar" e "X" consolidados na linha principal do elemento, eliminando a sub-linha extra; upload e exclusão do logo diferidos para o clique em "Salvar Layout" (mesmo comportamento do template de fundo)
  • Config → Templates: ao carregar um logo, toggle de visibilidade é ativado automaticamente; ao excluir, é desativado automaticamente
  • Dashboard — card Total e card Ticket Médio: cor do ícone e do número agora usa var(--bs-body-color) em ambos os temas (consistência claro/escuro); seletor de cor "Total" removido da aba Aparência

Corrigido

  • Config → página em branco (404): bloco if(false) morto removido; migration da coluna user_config reposicionada para o carregamento da config page, eliminando dependência do toggle_tema
  • Config → botão "Salvar Layout" ficava travado com spinner após concluir o salvamento — origHtml agora restaurado antes da chamada Xajax
  • Dashboard → gráfico doughnut crescia progressivamente a cada troca de tema: substituído renderDashboard() completo no MutationObserver por _updateChartsTheme(), que apenas atualiza as propriedades sensíveis ao tema (border, ticks, grid, legendas) via .update('none') sem recriar os canvas
v2.5.0 28/05/2026

Adicionado

  • Config → Templates: toggle de visibilidade (olho) em cada elemento — oculta o elemento tanto no preview quanto na renderização GD final do voucher; estado salvo no JSON de layout com compatibilidade retroativa (visible ausente = visível)
  • Config → Templates: overlay de logotipo — nova linha "Logotipo" no painel de coordenadas permite carregar uma imagem PNG/JPG como sobreposição no voucher; posição e tamanho configuráveis pelo editor drag-and-drop; arquivo salvo em voucher/custom/{slug}/logo.png e aplicado via GD com alpha blending
    • Novo handler config/upload_logo.php; caminho armazenado na chave logo_src de vouchers_config
    • Preview mostra thumbnail e posicionamento em tempo real; suporte a transparência PNG preservada na conversão GD
    • Renderização GD aplica logotipo em ambos os templates (horizontal e vertical) respeitando visible e proporção original da imagem
  • Badge de versão discreto no rodapé da sidebar, abaixo do menu do usuário: exibe v2.3.x em texto pequeno e semi-transparente; some suavemente quando a sidebar está colapsada
  • Versão centralizada em $app_version em includes/about.php — única fonte de verdade usada pelo badge da sidebar e pelo modal "Sobre" (que também foi atualizado de 2026.04.22 para a versão corrente)

Corrigido

  • Config → Templates: ícones de alinhamento de texto (esquerda/centro/direita) não estavam verticalmente centrados na linha do grid — corrigido adicionando display:flex; align-items:center ao .cfg-ic-align-col
v2.4.7 27/05/2026

Adicionado

  • Registro de horário nos campos de data de emissão (data_compra) e data de uso (data_uso): colunas migradas de DATE para DATETIME nos bancos de todos os tenants — permite auditoria com precisão de minuto
  • Script de migração landing/saas/patch_datetime_campos.sql para aplicar em novos tenants futuros
  • Bloqueio de edição de template em dispositivos touch/mobile (config/ → abas Template H e V): em telas com ponteiro impreciso (pointer: coarse) ou janelas estreitas (<992 px), o drag-and-drop dos handles e os campos de coordenadas ficam desabilitados — o preview do layout permanece visível; um banner amarelo explica o motivo
  • Detecção usa CSS Media Query pointer: coarse (ponteiro primário do sistema) em vez de navigator.maxTouchPoints, evitando falso positivo em notebooks com tela touch + mouse

Alterado

  • Exibição de datas na gerência (cards e tabela) agora inclui horário no formato DD/MM/YYYY HH:MM
  • Modal de edição de voucher: campos "Data emissão" e "Data Uso" alterados para datetime-local, exibindo e aceitando data + hora
  • Exportação CSV: colunas data_compra e data_uso exportadas com horário (DD/MM/YYYY HH:MM)
  • Filtros de intervalo de data na gerência corrigidos para usar DATE() nas comparações — evita exclusão silenciosa de registros do último dia do intervalo com colunas DATETIME
  • Todos os inputs de data da gerência agora usam flatpickr — visual e comportamento consistentes independentemente do SO ou locale do browser:
    • Filtros de busca: validade mín./máx., emissão mín./máx. e data de uso mín./máx. (6 campos)
    • Modal de edição: campo "Validade" (date-only, sem hora); Xajax atualiza via _fpDataValidade.setDate()
    • Offcanvas de cálculo de hash: campo "Validade"
  • Campos sem hora usam config compartilhada _fpDateOnlyConfig (dateFormat: 'Y-m-d'); formato enviado ao backend é idêntico ao anterior — nenhuma alteração no PHP de busca ou de save
  • Configurações → Template H/V: botão Carregar agora é preview-only — seleciona o arquivo e atualiza imediatamente o thumbnail e o editor de coordenadas via blob URL local, sem enviar nada ao servidor; a imagem só é persistida (upload + DB) quando o usuário clica em Salvar Layout, que faz o upload e depois salva as posições num único fluxo sequencial
  • Configurações → Template H/V: card "Imagem de Fundo" simplificado — removido o bloco redundante de nome/dimensões/botão Carregar; substituído por botão overlay semi-transparente com ícone de lápis diretamente sobre o thumbnail; ao hover o botão expande exibindo "Alterar imagem" com maior opacidade
  • Configurações → Template H/V: card "Imagem de Fundo" removido; botão overlay de lápis movido para o preview grande do editor (dentro de cfg-editor-wrap); z-index: 40 garante visibilidade sobre os drag handles; ícone fixo no canto inferior direito, label "Alterar imagem" expande para a esquerda no hover
  • privacidade/index.php: reescrita como página pública standalone — removidos session.inc.php, redirect.inc.php, navbar.php, Xajax e query de DPO; texto da seção 1 tornado genérico ("empresa contratante") sem dependência de dados do banco; estilo alinhado ao termos/index.php (CDN Bootstrap, sem sidebar)
  • termos/index.php: data de última atualização atualizada para Maio de 2026 (já era página pública — sem session/redirect)
  • Login: nome da empresa ($_footer_name / dpo.clinica) movido do rodapé para o topo do formulário como elemento principal (#login_company_name): Lato 300, 2.2rem, gradient branco→lavanda
  • Login: cabeçalho atualizado de "Login - Vouchers ✦" para "✦ Donara. Vouchers"; estilo rebaixado para badge secundário discreto (0.82rem, uppercase, letter-spacing, rgba 42% opacidade)
  • Login: cabeçalho refeito em layout flex horizontal — marca "Donara." (gradient, Lato 800) à esquerda, nome da empresa (branco, Lato 900, uppercase) à direita
  • Login: bordas removidas dos inputs e do botão Login
  • Gerência: cancelamento de vouchers expirados bloqueado em dois níveis:
    • Frontend (card view e table view): ícone de cancelar exibido em cinza (link-secondary) com tooltip "Voucher expirado — cancelamento não permitido"; botão "Usar Voucher" permanece ativo para expirados (operador decide caso a caso)
    • Backend (cancela_voucher Xajax): verificação de data_validade antes do UPDATE — retorna alerta e aborta se hoje > data_validade, impedindo cancelamento mesmo via requisição direta
    • Motivação: vouchers expirados não utilizados são cobrados na fatura; cancelá-los retroativamente distorcia a receita
  • Modal de edição de voucher — campo Status removido: status não é editável pelo modal de edição; o backend (editar_voucher) não atualiza voucher_usado — gerenciado exclusivamente pelas ações dedicadas (usar/cancelar)
  • Modal de edição de voucher — seção Paciente (Uso do voucher) agora exibida apenas quando voucher_usado = 1: a seção é envolta em <div id="section_paciente"> e mostrada/ocultada dinamicamente por load_voucher_data com base no status do voucher; ao ocultar, os campos ficam desabilitados para que não sejam submetidos via Xajax
  • Backend (editar_voucher): nome_paciente e data_uso só são gravados no banco se voucher_usado = 1 na consulta atual — proteção dupla independente da UI
  • Modal de edição de voucher — rótulo da seção superior alterado de "Comprador ou Beneficiário" para "Comprador ou Parceiro"
  • Cadastro de voucher: campo Validade migrado de type="date" (picker nativo do SO) para flatpickr (dateFormat: Y-m-d, disableMobile: true, minDate: today) — visual e comportamento agora consistentes com a gerência de vouchers independentemente do SO ou locale do browser
  • customJQuery_cadastro.js: bloco de setAttribute('min', ...) removido — a restrição de data mínima é gerenciada diretamente pela opção minDate: 'today' do flatpickr

Corrigido

  • Dashboard: guard 0000-00-00 00:00:00 adicionado na query de utilizações mensais para retrocompatibilidade com dados legados pré-migração
  • Ícones da sidebar colapsada agora estão centralizados: justify-content: center, gap: 0 e remoção de margin/padding horizontal no modo colapsado — !important necessário para vencer utilities Bootstrap (px-3, gap-2) e inline styles do botão de tema
  • Hover dos nav-links não ultrapassa mais a largura da sidebar colapsada: max-width corrigido para calc(var(--sidebar-w-collapsed) - 16px) e depois para var(--sidebar-w-collapsed) com margin: 0, garantindo que o elemento ocupe exatamente os 60px sem overflow
  • Caret do submenu do usuário não desaparece mais no hover: o pseudo-elemento ::after era compartilhado entre o tooltip CSS e o caret do Bootstrap Dropdown; corrigido com :not(.dropdown-toggle) na regra do tooltip
  • Modal de edição de voucher: campo "Data Uso" substituído por flatpickr (time_24hr: true, disableMobile: true) — elimina exibição AM/PM causada pelo locale do SO (Windows en-US) que o datetime-local nativo não consegue sobrescrever
  • Modal de edição de voucher: campo "Data emissão" alterado para type="text" com exibição em DD/MM/AAAA HH:MM (campo sempre desabilitado — apenas exibição)
  • Erro 500 ao salvar edição de voucher: adicionado try-catch (Throwable) ao redor do UPDATE — PHP 8.2 mudou o padrão de mysqli_report para MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT, fazendo com que erros MySQL lançassem mysqli_sql_exception não capturada; agora retorna alert() com a mensagem de erro em vez de HTTP 500
  • valor_compra não era atualizado corretamente quando o campo estava desabilitado: Xajax getFormValues inclui inputs disabled (ao contrário do HTML form padrão), enviando string vazia para editar_valor; MySQL strict rejeita '' em coluna numérica — corrigido com guard !== '' na condição, igual ao já existente em editar_data_validade
  • Espaçamento inconsistente dos nav-items da sidebar em algumas páginas (dashboard, gerenciar, consultar): adicionado !important em padding e margin do .app-sidebar .nav-link para prevenir sobrescrita por qualquer CSS externo, e line-height: 1.5 explícito no link e line-height: 1 no ícone para altura de item determinística independente do browser, fonte ou CSS de terceiros
  • Gerência: data de validade de vouchers expirados agora é imutável no modal de edição — impede o loophole "estender validade → cancelar" que permitia remover um voucher expirado do faturamento (billing exclui voucher_usado=2 mas não exclui expirados)
    • Backend (editar_voucher): SELECT verifica data_validade atual no banco antes de processar a edição; se hoje > data_validade, retorna alerta e aborta sem alterar o banco
    • Frontend (load_voucher_data): quando o voucher está expirado, o campo "Validade" é mantido desabilitado com tooltip "Voucher expirado — validade não pode ser alterada"; flatpickr tem clickOpens e allowInput desativados para bloquear também a interação com o calendário; ao abrir um voucher não expirado em seguida, as opções do flatpickr são restauradas
  • Gerência → modal de edição de voucher: bloqueada a seleção de datas passadas no campo Validade
    • Frontend (flatpickr): minDate: 'today' adicionado via Object.assign sobre _fpDateOnlyConfig — dias anteriores a hoje ficam cinza e não são selecionáveis no calendário
    • Backend (editar_voucher): guard data_validade < date("Y-m-d") antes do UPDATE — retorna alerta e aborta se a data enviada for anterior a hoje, impedindo expiração manual de vouchers válidos mesmo via requisição direta
v2.2.2 24/05/2026

Corrigido

  • Sidebar no tema dark exibe fundo preto (#0a0a0a) em todos os estados: expandida, colapsada e barra mobile — seletor html[data-bs-theme="dark"] com especificidade superior à do Bootstrap garante a cor em qualquer situação
  • Sidebar no tema light mantém o fundo #111827 inalterado
  • Botão de minimizar/expandir sidebar: ícone agora permanece visível no hover — adicionadas cores explícitas para os estados normal, hover e active, evitando que o Bootstrap sobrescreva com fundo que ocultava o ícone
  • Submenu do usuário não era mais cortado quando a sidebar estava colapsada — overflow-x: hidden da sidebar (elemento position: fixed) clipava o dropdown mesmo com data-bs-strategy='fixed'; corrigido via listeners show.bs.dropdown / hidden.bs.dropdown que liberam e restauram o overflow temporariamente
  • Erro de sintaxe PHP em navbar.php introduzido junto com o fix acima: aspas duplas dentro de querySelector(...) dentro de echo "..." encerravam a string prematuramente, derrubando todas as páginas
v2.2.0 23/05/2026

Adicionado

  • Controle de versões com Git (repositório privado no GitHub)
  • Integração da VPS Hostgator ao fluxo Git (deploy via git pull)
  • .gitignore protegendo credenciais, logs e templates por cliente
  • secrets.php.example como template documentado de configuração
  • Controle de limite de vouchers por plano: tenants active e cancelled em carência agora são verificados contra saas_plans.max_vouchers ao criar vouchers comerciais/campanha — anteriormente apenas o trial era verificado
  • Controle de limite de usuários por plano: criação e reativação de usuários verificam saas_plans.max_users; apenas ativo = 1 conta contra o limite (desativar um usuário libera uma vaga)
  • trial_max_users no SaaS Admin: novo campo configurável na seção Trial (ao lado de Duração e Máx. vouchers); campo vazio = ilimitado
  • Campos trial_max_vouchers e trial_max_users agora aceitam vazio = ilimitado (campo removido de saas_settings quando vazio, retornando NULL nas queries de verificação)
  • autocomplete="off" nos formulários Trial e Motivos de Cancelamento do SaaS Admin

Corrigido

  • Edição de vouchers provisórios na Gerência: o select de Tipo não possuía a opção "Provisório" (value=0), fazendo com que admins salvassem o voucher como Comercial involuntariamente (com código gerado automaticamente)
  • Título do modal de edição exibia vazio para vouchers provisórios; agora exibe #ID — provisório
  • Bloqueio de rebaixamento de tipo: vouchers Comerciais ou Campanha/Brinde não podem ser convertidos de volta para Provisório — verificação refeita para consultar voucher_tipo diretamente no banco (em vez de inferir pela existência de cod_validacao); opção "Provisório" desabilitada no select ao abrir o modal para esses vouchers
  • Erro 500 ao salvar edição de voucher provisório: datas nulas (data_compra, data_uso) eram enviadas como string vazia ao MySQL, que rejeita '' em colunas DATE com STRICT_TRANS_TABLES — adicionado guard !== '' antes de incluir qualquer data no UPDATE
  • data_compra (data de emissão) tornada imutável após definição: campo desabilitado no modal em três camadas — HTML sempre disabled, servidor nunca envia enableObj, e editar_voucher() ignora completamente o campo enviado pelo formulário; data de emissão só é definida na criação direta ou na conversão de tipo Provisório → Comercial/Campanha
  • Contas internas (is_internal = 1) isentas de qualquer verificação de limite de vouchers e usuários — consistente com o comportamento já existente em config/assinatura.php
  • TypeError: Cannot read properties of null (reading 'addEventListener') na página de Usuários: listener do modalToggleUsuario executava antes do HTML do modal existir no DOM
  • Erro 500 ao ativar/criar usuários e ao criar vouchers: exceções lançadas pelo bloco de consulta ao donara_master (PHP 8 + MYSQLI_REPORT_ALL) não eram capturadas e derrubavam a função Xajax — adicionado try-catch (Throwable) em todos os três pontos de verificação de limite
v2.0.0 21/04/2026

Adicionado

  • Página de Configuração do sistema
    • Aparência: controle de tema e cores dos vouchers e gráficos
    • Modelos de Regras: 4 modelos configuráveis para vouchers pré-definidos
    • Template Horizontal: imagem, cores e posicionamento dos elementos
    • Template Vertical: imagem, cores e posicionamento dos elementos
v1.9.0 20/02/2026

Adicionado

  • Dashboard detalhado:
    • Alertas de vouchers expirando em 30 dias e vouchers provisórios
    • Situação geral por status e por tipo
    • Indicadores financeiros (em circulação, utilizado, ticket médio)
    • Distribuição por modalidade de desconto
    • Evolução temporal dos últimos 12 meses
    • Desempenho por usuário (top 15)

Alterado

  • Vouchers Testes renomeados para Vouchers Provisórios
v1.8.0 18/08/2025

Adicionado

  • Filtros por coluna na página de Logs

Corrigido

  • Cor de background padrão do filtro
  • Ordem de exibição do tipo de voucher na tabela do dashboard
v1.7.0 16/08/2025

Adicionado

  • Tabela com quantidades de vouchers no Dashboard
v1.6.0 28/07/2025

Adicionado

  • Modelo de regras para Programa de Indicação

Melhorado

  • Quebra de linha no campo "Info" do voucher
v1.5.0 17/02/2025

Corrigido

  • Validação de usuário logado para criação de voucher Comercial ou Brinde
v1.4.0 16/01/2025

Adicionado

  • Verificação de Hash na página de Consultar Código
  • Exibição de status do voucher (Teste, Usado, Cancelado, Expirado) na arte de impressão

Corrigido

  • Título "Consultar Voucher" → "Gerenciar Vouchers"
v1.3.0 07/01/2025

Melhorado

  • Proteção na função de edição de voucher: alterações em Valor, CPF e Validade não são registradas em caso de desbloqueio manual pelo navegador
Nenhum resultado.