ModularReports
Companheiro do DRS
ModularReports: Inventário de Artefatos
| Campo | Valor |
|---|---|
| Produto | ModularReports |
| Documento | Inventário de Artefatos (companheiro do DRS) |
| Data de revisão | 2026-08-18 |
Documentos da família
- DRS (modularreports), o que o sistema faz.
- Roadmap de Implementação (modularreports-roadmap), em que ordem construir e como verificar.
- Jornadas dos Atores (modularreports-jornadas), como cada papel opera o sistema.
- Plano de Testes (modularreports-testes), o que validar antes de entregar.
- Questionário de Elicitação (modularreports-questionario), decisões de produto em aberto.
Este é o registro de completude: tudo que precisa existir para o sistema funcionar (tabelas, migration, permissões, infraestrutura, telas, configuração, analytics, mídia) e, para cada item, a ficha do que ele deve ser. Cada artefato aponta o RF e a tarefa do Roadmap que o cria; se a tarefa está pronta mas o artefato não bate com a ficha, algo falta. A ordem de construção está no Roadmap; aqui está a lista do que provisionar.
Como usar
Marque um artefato quando ele existir E bater com a Especificação. Cada cartão traz Para que serve, Relacionado com (RF e tarefa do Roadmap), Onde fica e Especificação (a ficha implementável no sub-formato do tipo). Fichas extensas referenciam os apêndices do DRS (Ap.1 a Ap.8) em vez de repetir a ficha.
Estado de fechamento (medido em 2026-08-18)
Placar: 46 fechados, 2 abertos, 48 no total (recontado em 2026-08-18, nascimento do {ART-AUD-03}). O artefato novo nasce fechado (ver "Governança" e o total no rodapé da tabela de tipos); os dois abertos seguem os mesmos de sempre, sem mudança nesta rodada: {ART-CFG-01} e {ART-SCR-03}, ambos com o motivo no próprio cartão.
Placar anterior (2026-08-09), preservado como histórico: 45 fechados, 2 abertos, 47 no total. O segundo aberto nasceu nesta data: {ART-SCR-03}, a barra superior de marca da agência na página pública, artefato que o protótipo aprovado desenhou desde o início e que nunca foi construído. Ele entra no inventário porque é o que falta para a sobreposição de logo do cliente por relatório poder existir sem apagar a marca da agência (RN-020 item 4). O placar fecha: 45 mais 2 é igual a 47, e 47 é também a soma da coluna de contagem da tabela de tipos logo abaixo, conferida um lado contra o outro nesta data. Sobre o primeiro aberto: O único aberto é {ART-CFG-01}, reaberto em 2026-08-05 por contagem real (4 de 20 parâmetros editáveis) e remedido em 2026-08-08 depois das cinco fatias do {F4-07} (15 de 20 agora, detalhe no próprio cartão). As Fatias 11 a 20 do Roadmap foram mergeadas e colocadas em produção em 2026-08-05 (as duas contagens já estavam corretas no Roadmap, marcadas por verificação com banco real; o que faltava era este documento, que seguia em 0 de 46 apesar de boa parte dos artefatos já existir desde fatias anteriores). Cada cartão foi conferido individualmente, artefato por artefato, contra a plataforma-mãe em execução: tabela por tabela no banco, rota por rota na API, container por container no orquestrador, e não por leitura de código isolada. O critério de fechamento é o da REGRA-INV-1: existe E é alcançável, ou seja, há um caminho pelo qual alguém ou algo em produção aciona o artefato.
Duas rodadas de medição sobre {ART-INFRA-01} e {ART-MEDIA-01}, registradas porque a lição vale para qualquer inventário futuro. A primeira consultou o serviço de armazenamento por objeto da plataforma-mãe, achou zero registro para qualquer módulo, e concluiu erradamente que a infraestrutura de armazenamento não existia; os dois cartões fecharam como abertos. Uma segunda frente, medindo o mesmo achado de forma independente, apontou que aquele serviço nunca foi usado por NENHUM módulo da plataforma-mãe, então a consulta sempre daria zero, com ou sem ModularReports: a pergunta certa não era "o serviço está vazio" e sim "qual mecanismo a casa usa de verdade". O mecanismo real é armazenamento em sistema de arquivos sobre volume dedicado, já em uso por outras áreas do Hub, e a medição direta encontrou arquivos reais do ModularReports gravados nele (artefatos de upload real, não de teste). Os dois cartões fecharam. A ficha do {ART-INFRA-01} estava errada, não ausente, e foi reescrita para descrever o mecanismo real; card que afirma um fato que nunca aconteceu (a versão anterior dizia "bucket provisionado", que nunca existiu com esse nome) segue sendo defeito por si, corrigido mesmo o artefato fechando pelo mecanismo certo.
Um terceiro achado desta rodada não impediu fechamento algum, mas fica registrado: o disparador de agenda ({ART-JOB-01}) foi encontrado VIVO em produção contra a expectativa inicial de que não existiria nenhum mecanismo de disparo, com a prova e o limite da prova no próprio cartão.
Tipos de artefato
| Tipo | Contagem | Onde vivem |
|---|---|---|
| Entidade / tabela (ART-ENT) | 15 | Schema reports no banco da plataforma-mãe |
| Migração de dados (ART-MIG) | 3 | Migration de registro do módulo no Hub, conversão v2 para v3 (golden set), schema da agenda e do perfil do cliente |
| Parâmetro de configuração (ART-CFG) | 2 (uma tabela com 20 parâmetros e o catálogo das 20 métricas de resultado) | Painel do admin de agência |
| Papel / permissão (ART-ACS) | 1 | Catálogo de permissões reports.* em código do Hub |
| Serviço / container (ART-INFRA) | 4 | Bucket de storage, sandbox de parse isolado, leitor público, ingestor de eventos |
| Menu / navegação (ART-NAV) | 1 | Chrome do Reports |
| Tela / página (ART-SCR) | 3 (uma tabela com 11 superfícies, a tela de agendas e a barra superior de marca da agência) | Chrome do operador, leitor público, painéis |
| Dashboard / relatório (ART-DASH) | 1 | Painel de analytics por agência |
| Métrica / indicador (ART-KPI) | 1 (tabela com 4 métricas) | Painel de analytics |
| Auditoria / log (ART-AUD) | 3 | Log de auditoria do Hub (eventos reports.*), a suíte de fixtures de controle negativo na integração contínua e a guarda de endereço de aviso do Hub inteiro |
| Notificação / alerta (ART-NOT) | 2 | Widget de comentário e Central de Notificações do Hub |
| Documento / PDF gerado (ART-DOC) | 1 | Gerador de PDF da plataforma-mãe |
| Campo personalizado (ART-FLD) | 3 | Ficha account_identidade_visual do Hub (identidade e tom de voz) e account_profile do schema reports (sazonalidade e modelo de venda) |
| Rotina agendada (ART-JOB) | 1 | Disparador de ciclo das agendas de geração |
| Asset (ART-MEDIA) | 1 | Bucket reports, criativos e logos |
| Tipo de bloco do renderizador (ART-BLK) | 1 (tabela com 16 tipos) | Registro declarativo do package renderizador |
| Modelo de relatório (ART-TPL) | 3 | Tabela template do schema reports: 2 Globais semeados e 1 individual da agência de origem |
Total: 48 artefatos (47 até esta rodada, mais {ART-AUD-03}, nascido em 2026-08-18 da medição do {F8-58} do Roadmap), que é a soma da coluna de contagem acima e também a contagem dos cartões deste documento, conferidas uma contra a outra nesta data. {ART-AUD-03} nasce FECHADO: a guarda foi ligada e provada (dry-run vazio, guarda estática 90/0, par de casos em execução 26/0, reprovação de isenção morta por quebra) antes de entrar no documento; ficha completa na seção Governança. A revisão de 2026-08-03 já tinha corrigido a divergência entre 31 e 33 recontando os dois lados; a diferença entre o 44 daquela revisão e o 46 seguinte é que a tabela de tipos não somava os dois cartões de ferramental (ART-INFRA-05, ART-INFRA-06), presentes no corpo do documento desde 2026-08-03 mas nunca incluídos na soma do topo. Corrigido recontando os dois lados de novo, mesma disciplina.
Entidades e tabelas (schema reports)
Espelha o bloco 5 (E.1, E.2) do DRS. Migration que cria as tabelas de dados: {F1b-01} do Roadmap (antes das fatias que as consomem).report_jobeagency_domainnascem nas suas fatias (evolução e Fatia 14).
Para que serve: o relatório e sua identidade pública, incluindo o token do link e a duração de produção.
Relacionado com: RF-CMP-001, RF-PUB-001 a 004, RF-ADM-015; tarefas {F1b-01}, {F8-01}, {F9-02} a {F9-04} do Roadmap; entidade vizinha report_draft (1:1) e snapshot (1:1).
Onde fica: schema reports, banco da plataforma-mãe.
Especificação: Nome: report | Descrição: o relatório e sua identidade pública | Atributos: id (uuid, PK), company_id (uuid, FK companies, NOT NULL), account_id (uuid, FK accounts, NOT NULL), template_id (uuid, FK template, NULL), type (text, propósito, valor fechado de três: campanha, fechamento, pesquisa, RN-014), cadence (text, valor fechado de seis: quinzenal, mensal, trimestral, semestral, anual, pontual, NOT NULL, declarado no passo 1 do wizard e nunca inferido da largura do período, RN-014 e RF-CMP-001), title (text), period (daterange, a janela real, cuja largura em dias alimenta a guarda de RF-CMP-021), public_token (text, único, mínimo 128 bits), password_hash (text, NULL), expires_at (timestamptz, NULL), status (enum: draft, published, revoked), created_at (timestamptz), published_at (timestamptz, NULL), production_minutes (int, NULL) | Chave primária: id | Chaves estrangeiras: company_id -> hub.companies (N:1), account_id -> hub.accounts (N:1), template_id -> reports.template (N:1, opcional) | Índices: public_token (único), (company_id, account_id), (account_id, period) para a pasta por ano (RF-ADM-013) | Regras de integridade: public_token gerado uma vez e mantido em republish (RN-001); status published exige snapshot associado; company_id igual ao company_id do Account referenciado, garantido por trigger (RN-009); type e cadence restritos aos valores fechados de RN-014 por restrição no próprio schema, nunca só na aplicação ({F17-05}) | Escopo de acesso: RLS RESTRICTIVE por company_id (RNF-SEG-001).
Para que serve: o rascunho editável do relatório, 1:1 com report, onde o compositor lê e escreve.
Relacionado com: RF-CMP-002 a 004, RF-CUR-001 a 013, RN-020; tarefas {F1b-01}, {F8-01} a {F8-13}; entidade report (pai).
Onde fica: schema reports.
Especificação: Nome: report_draft | Descrição: rascunho editável do relatório | Atributos: report_id (uuid, PK e FK report, 1:1), blocks (jsonb, contrato de bloco v3), data_points (jsonb, registro de proveniência dp:<id>), briefing (jsonb, KPI principal, intenção, destaques, identidade escolhida no assistente, que é preferência do instante zero e nunca permissão: quem escolheu o padrão do cliente continua podendo sobrepor), identity_overrides (jsonb, NOT NULL, default objeto vazio, sobreposição de identidade por relatório, RF-CUR-004 e RN-020, só as 2 chaves fechadas accent_color e logo_asset_id, accent_color no formato hexadecimal de 6 dígitos, logo_asset_id referenciando asset por identificador e nunca URL crua ou base64, RN-006), theme_overrides (jsonb, ajuste fino de apresentação por relatório, RF-CUR-008, só as 4 chaves fechadas paddingStep, gapStep, fontSizeStep, buttonAccentStep, additionalProperties:false, default da página quando o bloco não sobrescreve), updated_at (timestamptz) | Chave primária: report_id | Chaves estrangeiras: report_id -> reports.report (1:1) | Índices: nenhum além da PK | Regras de integridade: todo número em blocks referencia um dp: presente em data_points (RN-003); theme_overrides só aceita as 4 chaves fechadas de RF-CUR-008; identity_overrides só aceita as 2 chaves fechadas de RF-CUR-004, recusa accent_color fora do formato hexadecimal de 6 dígitos, e a ausência da chave é a única representação de não configurado, ou seja, limpar remove a chave e nunca grava vazio (RN-020 item 2); todo bloco de autoria do operador carrega o valor reservado operator-inserted em tags e o seu id é cunhado em espaço de nomes distinto do usado pela composição, e toda gravação de blocks preserva esses blocos (RF-CUR-010, Ap.2); todo dígito que represente métrica em campo de texto marcado como editável na ficha do Ap.3 (body_md, caption e os demais) referencia um dp: por token, sob o mesmo critério de métrica em todos eles (RF-CUR-011) | Escopo de acesso: RLS RESTRICTIVE por company_id via join com report.
Para que serve: a foto imutável publicada do relatório, única por report, sem versionamento.
Relacionado com: RF-PUB-001, RF-REN-001 a 003; tarefas {F2-01} a {F2-04}, {F9-01}; entidade report (pai), comment (âncora nos blocos do payload).
Onde fica: schema reports.
Especificação: Nome: snapshot | Descrição: foto imutável publicada, única por report | Atributos: report_id (uuid, PK e FK report, 1:1), payload (jsonb: blocks, data_points, metas vigentes, branding (a identidade resolvida do relatório no instante da publicação, carregando a procedência da cor entre quatro valores nominais, sobreposição do relatório, ficha do cliente, padrão da agência ou lacuna, e a do logo entre dois, agência ou sobreposição do relatório, RF-CUR-012 e RN-020; republicar rerresolve e sobrescreve, editar o rascunho depois não muda nada, RN-020 item 3), share_card, os overrides de apresentação resolvidos, theme_overrides do relatório e style_overrides por bloco, RF-CUR-008, todos copiados no publish; sem data_points o leitor não resolve número), schema_version (int), published_at (timestamptz) | Chave primária: report_id | Chaves estrangeiras: report_id -> reports.report (1:1) | Índices: nenhum além da PK | Regras de integridade: payload nunca reescrito por edição do draft após publish, só por novo publish (RN-002); schema_version confere com o registro do tipo de bloco no momento do publish | Escopo de acesso: leitor público (RF-REN-006) via role só-SELECT; escrita só pelo fluxo de publish autenticado (RF-CUR-007).
FECHADO pela tabela, pela rota de upload e pelo pipeline de parse, os três confirmados em produção. Ressalva de 2026-08-05 CORRIGIDA no mesmo dia: uma primeira medição desconfiou da persistência do arquivo cru por ter consultado o serviço de armazenamento errado, ver {ART-INFRA-01}; medição direta no sistema de arquivos real mostrou arquivos de artefato gravados de verdade (formatos de planilha, tamanhos e timestamps de upload real), então a guarda física do arquivo cru está confirmada, não pendente.
Para que serve: o insumo cru (planilha, CSV, PDF, Markdown, JSON, imagem) que o operador sobe, descartável por TTL.
Relacionado com: RF-ING-001 a 006, RF-BND-002; tarefas {F4-01} a {F4-06}; entidade report (pai).
Onde fica: schema reports (metadado); arquivo cru no armazenamento de arquivo do módulo, sistema de arquivos sobre volume dedicado da plataforma-mãe.
Especificação: Nome: artifact | Descrição: insumo cru, descartável | Atributos: id (uuid, PK), report_id (uuid, FK report), sha256 (text), type (enum: planilha, csv, pdf, markdown, json, imagem), parse_status (enum: pendente, ok, erro), purge_after (timestamptz, TTL curto por LGPD) | Chave primária: id | Chaves estrangeiras: report_id -> reports.report (N:1) | Índices: report_id, purge_after (para o job de limpeza) | Regras de integridade: registro apagado (ou marcado apagado) quando purge_after expira, sem quebrar o report publicado (RN-004) | Escopo de acesso: RLS RESTRICTIVE por company_id via join com report; sem tela de listagem como acervo (RF-BND-002).
Para que serve: o comentário logado, ancorado a um bloco do snapshot.
Relacionado com: RF-COM-001 a 006; tarefas {F12-01} a {F12-06}; entidade report (pai), snapshot (referencia block_id do payload).
Onde fica: schema reports.
Especificação: Nome: comment | Descrição: comentário logado ancorado | Atributos: id (uuid, PK), report_id (uuid, FK report), block_id (text, id estável do bloco), excerpt (text, NOT NULL), author_contact_id (uuid, FK hub.contacts, NULL), body (text), orphaned (boolean, default false), resolved_at (timestamptz, NULL), created_at (timestamptz) | Chave primária: id | Chaves estrangeiras: report_id -> reports.report (N:1, ON DELETE CASCADE), author_contact_id -> hub.contacts (N:1, ON DELETE SET NULL) | Índices: report_id, (report_id, block_id), author_contact_id | Regras de integridade: excerpt sempre preenchido no create; orphaned vira true quando block_id some do snapshot numa republicação, sem apagar o comentário (RN-005); no offboarding LGPD (RF-ADM-011), apagar o Contact põe author_contact_id em NULL e o body é anonimizado, sem apagar o comentário (RN-007) | Escopo de acesso: leitura pública ligada ao snapshot; escrita só via endpoint autenticado separado do leitor (RF-COM-002).
Para que serve: o evento de audiência do link (visualização, referrer, sessão da agência).
Relacionado com: RF-ANL-001 a 005; tarefas {F13-01} a {F13-05}; entidade report (pai).
Onde fica: schema reports.
Especificação: Nome: link_event | Descrição: evento de audiência | Atributos: id (uuid, PK), report_id (uuid, FK report), event_type (enum: view, ...), referrer (text, NULL), opaque_visitor_id (text, identificador não pessoal), is_agency_session (boolean, para exclusão do painel), created_at (timestamptz) | Chave primária: id | Chaves estrangeiras: report_id -> reports.report (N:1) | Índices: report_id, created_at, (report_id, event_type, is_agency_session) para o KPI de visualizações (ART-KPI-01) | Regras de integridade: opaque_visitor_id nunca é o email ou identificador pessoal cru (RF-ANL-004); is_agency_session=true marca o acesso vindo de navegador com sessão ativa da plataforma, de qualquer agência, e exclui o evento das métricas do painel (RF-ANL-003); a sessão de comentário do cliente não produz essa marca, e por isso a leitura do cliente conta | Escopo de acesso: escrita pelo pipeline de eventos separado do leitor (RF-ANL-001); leitura só pelo painel de analytics da própria agência (RF-ANL-006).
Para que serve: o tipo de relatório com os blocos padrão da agência, o gabarito que a IA preenche.
Relacionado com: RF-ADM-009, RF-ADM-010, RF-ADM-016, RF-ADM-017, RF-CMP-001, RN-013; tarefas {F10-00}, {F10-01}, {F10-02}, {F10-03}, {F10-04}, {F8-01}; entidade report (referenciado por template_id).
Onde fica: schema reports.
Especificação: Nome: template | Descrição: tipo de relatório com blocos padrão, individual da agência ou Global (RN-013) | Atributos: id (uuid, PK), company_id (uuid, FK companies, agência de origem, preservado também nos Globais promovidos, NULL apenas nos Globais semeados pela operação da plataforma), name (text), type (text), default_blocks (jsonb, ENVELOPE de duas chaves e nunca array cru, ver Forma de default_blocks adiante), is_global (boolean, default false, só o Super Admin escreve), created_at (timestamptz) | Chave primária: id | Chaves estrangeiras: company_id -> hub.companies (N:1, opcional, ON DELETE CASCADE) | Índices: company_id, is_global | Regras de integridade: default_blocks nunca contém números ou textos específicos de um cliente (RF-ADM-010); um template nasce com is_global false; promover marca o próprio registro e nunca cria outro, preservando company_id; report.template_id é ON DELETE SET NULL porque os blocos são copiados para o rascunho no nascimento do relatório | Escopo de acesso: leitura quando is_global é true OU company_id é a Company da sessão; escrita da agência só quando company_id é a Company da sessão E is_global é false; leitura e escrita irrestritas para o Super Admin (hub.is_super_admin()), que é quem promove e despromove | Forma de default_blocks: envelope JSON com exatamente duas chaves, blocks e direction_profile, nunca um array cru de blocos. blocks: array dos blocos padrão do gabarito, até 30 itens, cada bloco no contrato de bloco v3 com id (texto de 1 a 80 caracteres, alfanumérico mais hífen e sublinhado, obrigatório), type (um dos tipos do contrato de bloco v3, obrigatório), hidden (booleano, opcional), tags (array de até 10 textos de até 40 caracteres, opcional) e props (objeto livre, opcional); nenhuma outra chave é aceita, e no template gerado por salvar como template (RF-ADM-010) props sai sempre vazio, porque é nele que vivem os números e os textos do cliente. direction_profile: o perfil de direção reusável de RF-ADM-010, sem dado do cliente, objeto com as chaves opcionais objetivo (texto de até 240), decisao_alvo (texto de até 300), tom (texto de até 120, aceita nulo), blocos_preferidos (array de tipos de bloco), enfatizar e evitar (arrays de até 10 textos de até 60 cada); nenhuma outra chave é aceita, e o valor é nulo quando o briefing de origem não trazia direção. Motivo do envelope: RF-ADM-009 (blocos padrão) e RF-ADM-010 (perfil de direção guardado junto, sem dado do cliente) precisam viver no mesmo template, e o schema tem uma coluna jsonb só. Leitura defensiva: uma linha cujo valor seja array cru é lida como blocks com direction_profile nulo, então registro legado não quebra a tela. Resposta da API: nunca devolve o envelope cru, expõe blocks e direction_profile como campos irmãos, mais scope derivado de is_global | Estado (medido em 2026-07-30): aplicado. {F10-00} trocou is_factory por is_global (booleano, não nulo, falso por default), preservou company_id como agência de origem e reescreveu o escopo em quatro regras restritivas, uma por comando: leitura quando a linha é Global, ou é da Company da sessão, ou quem lê é o Super Admin; inserção, atualização e exclusão só quando a linha é da Company da sessão E não é Global, ou quem grava é o Super Admin, sendo que a condição sobre a linha antiga na atualização fecha a autopromoção.
Para que serve: as metas e o planejamento por cliente, referência persistente de planejado versus realizado, sem TTL (não é artifact).
Relacionado com: RF-ING-007, RF-BND-003, RN-008; tarefas {F1b-01}, {F11-02} do Roadmap; bloco comparison (Ap.3) que lê daqui; snapshot (copia as metas no publish).
Onde fica: schema reports.
Especificação: Nome: report_goal | Descrição: metas e planejamento por cliente (referência, não tracking) | Atributos: id (uuid, PK), company_id (uuid, FK companies, NOT NULL), account_id (uuid, FK accounts, NOT NULL), metric (text), target_value (numeric), unit (text), period_label (text), source_artifact_id (uuid, NULL), created_at (timestamptz), updated_at (timestamptz) | Chave primária: id | Chaves estrangeiras: company_id -> hub.companies (N:1), account_id -> hub.accounts (N:1) | Índices: (company_id, account_id) | Regras de integridade: persistente por cliente, sem purge_after (não é artifact); o snapshot copia as metas vigentes no publish e alterá-las depois não muda o snapshot (RN-008); não gera tracking contínuo (RF-BND-003) | Escopo de acesso: RLS RESTRICTIVE por company_id.
Para que serve: o estado da composição quando ela deixar de ser síncrona (só existe quando a fila assíncrona entrar); no V1 a composição é síncrona in-process e custo/tempo gravam no report_draft.
Relacionado com: RF-CMP-012, RNF-PERF-002; tarefa de evolução (a fila não é V1); DRS E.2, RNF-PERF-002.
Onde fica: schema reports (só quando a fila for construída).
Especificação: Nome: report_job | Descrição: estado da composição assíncrona (evolução) | Atributos: id (uuid, PK), report_id (uuid, FK report), status (enum: queued, composing, done, failed), progress (int), tokens (int), duration_ms (int), error (text, NULL), created_at (timestamptz) | Chave primária: id | Chaves estrangeiras: report_id -> reports.report (N:1) | Índices: report_id, status | Regras de integridade: não existe no V1 (composição síncrona não persiste estado de job); entra com a fila assíncrona, disparada pelo primeiro request que estourar o tempo (RNF-PERF-002) | Escopo de acesso: RLS RESTRICTIVE por company_id via join com report.
Para que serve: o domínio custom da agência com validação de posse, para o link público sair sob o domínio da agência.
Relacionado com: RNF-SEG-006, ADR-015; tarefas {F14-01}, {F14-02} do Roadmap.
Onde fica: schema reports.
Especificação: Nome: agency_domain | Descrição: domínio custom da agência com validação de posse | Atributos: id (uuid, PK), company_id (uuid, FK companies, NOT NULL), domain (text, único), txt_token (text, para a validação TXT de posse), status (enum: pending, verified), created_at (timestamptz) | Chave primária: id | Chaves estrangeiras: company_id -> hub.companies (N:1) | Índices: domain (único), company_id | Regras de integridade: o CNAME só ativa com status verified após a posse por registro TXT (RNF-SEG-006); desvincular remove o CNAME | Escopo de acesso: RLS RESTRICTIVE por company_id.
Para que serve: a agenda que faz o sistema gerar sozinho o relatório de um cliente a cada período. É a peça que torna a cadência um agendamento de verdade e não um rótulo.
Relacionado com: RF-ADM-019 a RF-ADM-022, RN-017, ADR-021; tarefas {F19-01} a {F19-05} do Roadmap; artefatos {ART-ENT-12} (os ciclos dela), {ART-ENT-13} (o trabalho de composição) e {ART-JOB-01} (o disparador).
Onde fica: schema reports.
Especificação: Nome: report_schedule | Descrição: agenda de geração de um cliente para um propósito | Atributos: id (uuid, PK), company_id (uuid, FK companies, NOT NULL), account_id (uuid, FK accounts, NOT NULL), type (enum de 3, propósito, RN-014, NOT NULL), cadence (enum de 6, RN-014, NOT NULL), calendar_anchor (smallint, o dia do ciclo em que a edição nasce), template_id (uuid, FK template), owner_user_id (uuid, o responsável por revisar, NOT NULL), state (enum: active, paused, NOT NULL, default active), created_at, updated_at | Chave primária: id | Chaves estrangeiras: company_id -> hub.companies (N:1), account_id -> hub.accounts (N:1), template_id -> reports.template (N:1), owner_user_id -> hub.users_profile (N:1) | Índices: (account_id, type) ÚNICO, company_id, state | Regras de integridade: unicidade sobre (account_id, type) porque duas agendas do mesmo par produziriam duas edições concorrentes no mesmo ciclo (RN-017 item 1); trigger de RN-009 exigindo que account_id pertença a company_id; pausar e retomar só alteram state, nunca apagam a linha (RN-017 item 2) | Escopo de acesso: RLS RESTRICTIVE por company_id.
Para que serve: registrar cada ciclo vencido de uma agenda e o desfecho dele. Existe porque ciclo sem desfecho é o silêncio que faz a operação confiar numa geração que já parou, e ninguém reporta o que não aparece.
Relacionado com: RF-CMP-029, RF-ADM-022, RN-017 item 3; tarefas {F19-01}, {F19-05}, {F19-08} do Roadmap; artefatos {ART-ENT-11} (a agenda) e {ART-NOT-02} (o aviso).
Onde fica: schema reports.
Especificação: Nome: schedule_cycle | Descrição: um ciclo vencido de uma agenda, com o desfecho | Atributos: id (uuid, PK), schedule_id (uuid, FK report_schedule, NOT NULL), window (daterange, a janela do ciclo, NOT NULL), outcome (enum de 6: generated, skipped_paused, no_artifact, parse_failed, ai_unavailable, guard_failed, NOT NULL), report_id (uuid, FK report, nulo quando não gerou), closed_at (timestamptz, NOT NULL) | Chave primária: id | Chaves estrangeiras: schedule_id -> reports.report_schedule (N:1), report_id -> reports.report (N:1) | Índices: (schedule_id, closed_at), outcome | Regras de integridade: outcome NOT NULL sem valor de fallback, de modo que não existe ciclo sem desfecho; report_id preenchido se e somente se outcome é generated; ciclos vencidos durante a pausa gravam skipped_paused e é essa lista que a agenda exibe ao operador ao retomar (RF-ADM-022) | Escopo de acesso: RLS RESTRICTIVE pelo company_id da agenda.
Para que serve: guardar o estado de uma composição que roda fora da sessão do operador, que é o caso de toda edição nascida de agenda. Estava escrito como evolução opcional e passou a ser escopo com a resposta do dono a {Q4b}.
Relacionado com: RF-CMP-027, ADR-021, RNF-PERF-002; tarefas {F19-01} e {F19-06} do Roadmap; artefatos {ART-ENT-11} e {ART-ENT-02} (report_draft, onde o resultado pousa).
Onde fica: schema reports.
Especificação: Nome: report_job | Descrição: estado de uma composição disparada fora da sessão do operador | Atributos: id (uuid, PK), report_id (uuid, FK report, NOT NULL), schedule_id (uuid, FK report_schedule, nulo quando a composição é avulsa), status (enum: queued, composing, done, failed, NOT NULL), progress (smallint 0 a 100), tokens (integer), duration_ms (integer), error (text, nulo quando status não é failed), created_at | Chave primária: id | Chaves estrangeiras: report_id -> reports.report (N:1), schedule_id -> reports.report_schedule (N:1) | Índices: report_id, (status, created_at) | Regras de integridade: a composição continua sendo a mesma do caminho manual (ADR-013 intacto), esta tabela guarda apenas o progresso e o custo de quem a disparou sem sessão; error obrigatório quando status é failed | Escopo de acesso: RLS RESTRICTIVE pelo company_id do relatório.
Para que serve: o lugar ÚNICO dos atributos do cliente que mudam a leitura dos relatórios dele. Nasce com dois: a sazonalidade (que escolhe a base de comparação) e o modelo de venda (que troca o vocabulário do funil canônico). Consolidar os dois numa tabela evita que cada atributo novo do cliente vire um campo solto num lugar diferente.
Relacionado com: RF-ADM-018, RF-ADM-023, RN-016, RN-018, ADR-022; tarefas {F18-09} e {F20-02} do Roadmap; artefatos {ART-FLD-02} (sazonalidade) e {ART-FLD-03} (modelo de venda), que são os dois campos desta tabela.
Onde fica: schema reports.
Especificação: Nome: account_profile | Descrição: perfil do cliente dentro do Reports | Atributos: account_id (uuid, PK, FK accounts), company_id (uuid, FK companies, NOT NULL), is_seasonal (boolean, NOT NULL, default falso), peak_month (smallint 1 a 12, nulo, só faz sentido com is_seasonal verdadeiro), sale_model (enum de 2: consultiva, direta, NULO PERMITIDO e nulo é estado válido, não pendência), updated_at | Chave primária: account_id (um perfil por cliente) | Chaves estrangeiras: account_id -> hub.accounts (1:1), company_id -> hub.companies (N:1) | Índices: company_id | Regras de integridade: trigger de RN-009 exigindo que account_id pertença a company_id; sale_model fora dos dois valores é recusado pelo banco; mudar qualquer campo NÃO altera relatório já publicado, porque o valor foi copiado para o snapshot imutável no publish (RF-PUB-001, RN-018 item 6) | Escopo de acesso: RLS RESTRICTIVE por company_id.
Para que serve: guardar um fato vigente do negócio do cliente que é operando de fórmula e não alvo a atingir: o fee da agência, a margem de contribuição e o multiplicador de valor do cliente ao longo do tempo. Existe separado de report_goal porque uma meta tem realizado para comparar e um parâmetro não tem: gravar um fee como meta faria o sistema oferecer uma leitura de planejado contra realizado que não significa nada.
Relacionado com: RF-ING-008, RF-CMP-031, RN-008, Ap.10; tarefas {F20-05} e {F20-06} do Roadmap; artefatos {ART-ENT-08} (report_goal, o vizinho de que este se distingue) e {ART-CFG-02} (o catálogo que nomeia as métricas aceitas).
Onde fica: schema reports.
Especificação: Nome: business_parameter | Descrição: fato vigente do negócio do cliente, operando de fórmula | Atributos: id (uuid, PK), company_id (uuid, FK companies, NOT NULL), account_id (uuid, FK accounts, NOT NULL), metric (text, NOT NULL, restrito às métricas de classe parâmetro do catálogo do Ap.10), value (numeric, NOT NULL), unit (text), valid_from (date, NOT NULL), source_artifact_id (uuid, nulo), created_at | Chave primária: id | Chaves estrangeiras: company_id -> hub.companies (N:1), account_id -> hub.accounts (N:1) | Índices: (account_id, metric, valid_from), company_id | Regras de integridade: trigger de RN-009 exigindo que account_id pertença a company_id, pelo mesmo motivo e do mesmo jeito que report_goal ganhou o seu em {F11-01}; valid_from obrigatório, porque parâmetro sem vigência não permite saber qual valor valia na data do relatório; o valor vigente na data do publish é congelado no snapshot (RN-008); o arquivo importado nunca é persistido nem vira artifact (RF-BND-002) | Escopo de acesso: RLS RESTRICTIVE por company_id.
Migração e permissões
Para que serve: cunha o module_key NOVO reports (não reusa results): adiciona o literal reports à restrição CHECK de módulos válidos da tabela de entitlements de módulo do Hub, e a entrada reports (rótulo "Relatórios", não core) ao catálogo de módulos em código. Depois disso o entitlement booleano por Company pode ser ligado (desligado por default).
Relacionado com: RF-ADM-001; tarefa {F6-01} do Roadmap; artefato vizinho ART-CFG-01 (o toggle por Company é admin-editável).
Onde fica: migration versionada do banco da plataforma-mãe (a CHECK de hub.module_entitlements) mais a lista de módulos em código (canonical-enums do Hub).
Especificação: Origem: catálogo de módulos do Hub em código | Destino: restrição CHECK de módulos válidos da tabela de entitlements de módulo, mais a lista de módulos em código | Entidades migradas e volume: 1 alteração de CHECK constraint mais 1 entrada de catálogo em código, sem dado de linha migrado | Mapeamento de campos: adiciona o literal 'reports' ao CHECK e a entrada {key:"reports", label:"Relatórios", core:false} ao catálogo | Transformações: nenhuma | Validação pós-migração: SELECT confirma que uma Company pode receber entitlement booleano 'reports'; nenhuma Company existente recebe o módulo ligado automaticamente; o catálogo em código lista 'reports' | Itens de revisão manual: nenhum | Reversibilidade: aditiva, reversível por migration de remoção do literal se nenhuma Company tiver o módulo ligado | Quando roda: one-shot, na {F6-01}, depois das tabelas de dados (F1b).
Para que serve: converter os relatórios reais do formato v2 para o contrato de bloco v3 e montar o conjunto de referência (golden set) da Fatia 1, insumo do juiz de rubrica e dos gates HARD.
Relacionado com: RF-CMP-006 (proveniência legada), DRS I.1, Ap.2, Ap.8; tarefa {F1-00} do Roadmap; contrato de bloco v3 ({F2-01}).
Onde fica: script determinístico de conversão mais o corpus resultante na pasta de fixtures do projeto, versionada junto com o código.
Especificação: Origem: relatórios reais em v2 (hero, kpis, sections) mais os artefatos crus quando existirem | Destino: documentos v3 (blocks[] com id estável, data_points com dp: e source) | Entidades migradas e volume: os relatórios reais já entregues, mais um export adversarial com número inflado (fail-loud), mais ao menos dois casos fora do nicho | Mapeamento de campos: hero/kpis/sections do v2 para blocks[] v3, cada bloco recebe id; para cada número v2 (já formatado) cria data_point com source {kind:"artifact", ...} quando o cru existe, ou marca provenance:"legacy" quando só há o formatado | Transformações: atribuição de id por bloco, extração de dp: por número | Validação pós-migração: cada documento v3 valida contra o contrato de bloco; todo número tem dp: ou provenance legacy; o caso adversarial é reconhecido como tal pela rubrica | Itens de revisão manual: os números sem cru disponível ficam marcados legacy e revisados a olho | Reversibilidade: os v2 originais são preservados; a conversão não os apaga | Quando roda: one-shot na {F1-00}, base da Fatia 1.
Para que serve: as capabilities do Reports por papel canônico do Hub, sem papel novo.
Relacionado com: RF-ADM-002, RF-ADM-003; tarefa {F6-03} do Roadmap; DRS B.1 (catálogo de atores). RF-ADM-016 (promoção de template) fica de propósito fora da matriz, pelo motivo registrado na Especificação.
Onde fica: catálogo de permissões em código do Hub (defaults por papel mais metadados de capability), prefixo reports., sem tabela nova; override por usuário reusa a concessão de capability existente.
Especificação: tabela Capability | super_admin | admin | team_manager/workforce | contact:
| Capability | super_admin | admin | operador | contact |
|---|---|---|---|---|
reports.entitlement.manage | Sim, escopo todos | Não | Não | Não |
reports.agency.manage_users | Sim, escopo todos | Sim, só a própria agência | Não | Não |
reports.identity.manage | Não | Sim, só a própria agência e seus clientes | Não | Não |
reports.template.manage | Sim, escopo todos | Sim, só os individuais da própria agência (nunca um Global) | Sim, só os individuais da própria agência (nunca um Global) | Não |
reports.compose | Não | Não | Sim, só clientes da própria agência | Não |
reports.publish | Não | Não | Sim, só sessão humana | Não |
reports.analytics.view | Sim, escopo todos | Sim, só a própria agência | Não | Não |
reports.client.create | Não | Sim | Sim | Não |
reports.client.offboard | Não | Sim, só a própria agência | Não | Não |
reports.comment.create | Não | Não | Não | Sim, só se logado |
reports.comment.resolve | Não | Sim | Sim | Não |
Promoção e despromoção de template (RF-ADM-016, RN-013, tarefa {F10-03}) não têm linha nesta matriz, e a ausência é deliberada: são gate de papel Super Admin, não permissão concedível. A concessão individual de permissão do Hub alcança qualquer permissão e qualquer usuário, então uma permissão de promoção seria delegável a quem não é Super Admin, e a exclusividade que RN-013 exige sem exceção cairia. O sistema confere o papel diretamente, e a escrita da tabela template é a segunda camada, porque só aceita a linha marcada como Global quando quem grava é o Super Admin.
Quem configura este papel: Super Admin (entitlement) e Admin de agência (usuários dentro da agência) | Plano(s) onde o papel existe: só no Hub (ModularReports não tem plano próprio) | Restrição de visibilidade de dados: por company_id (RNF-SEG-001) | Pode delegar: Admin de agência pode promover outro usuário da própria agência a admin; não pode criar Super Admin. Não repete a matriz completa de capabilities do Hub (essa vive no catálogo do Hub); traz só o recorte reports.* (REGRA-INV-2).
Para que serve: criar no schema reports as cinco tabelas que as respostas de 2026-08-03 trouxeram, com as restrições que impedem os defeitos conhecidos já no banco e não só no código.
Relacionado com: {ART-ENT-11} a {ART-ENT-15}, RN-009, RN-017, RN-018; tarefas {F19-01} e {F20-02} do Roadmap.
Onde fica: db/migrations/ do repositório do módulo, aplicada ao schema reports.
Especificação: Origem: schema reports sem agenda, sem perfil de cliente e sem parâmetro de negócio | Destino: as cinco tabelas criadas | Volume: DDL apenas, nenhuma linha de dado migrada | Estratégia: aditiva e idempotente, transação única, sem tocar tabela existente | Idempotência: CREATE TABLE IF NOT EXISTS e CREATE INDEX IF NOT EXISTS | Ordem: report_schedule, depois schedule_cycle e report_job (que a referenciam), depois account_profile e business_parameter | Rollback: DROP das cinco, sem perda de dado pré-existente porque nenhuma tabela antiga é alterada | Validação pós-migração: o INSERT de segunda agenda no mesmo par cliente mais propósito é recusado; um outcome fora dos seis é recusado; um sale_model fora dos dois é recusado; um INSERT com company_id de uma agência e account_id de conta de outra é recusado nas quatro tabelas que têm account_id; a leitura sob a regra de acesso de outra agência devolve zero linhas nas cinco.
Infraestrutura
Para que serve: armazena o artefato cru (com TTL) e os assets (criativos, capas de compartilhamento) do módulo.
Relacionado com: RF-ING-004, RF-ING-005, ADR-009; tarefas {F4-04} e {F15-02} do Roadmap; artefato ART-ENT-04 (artifact), ART-MEDIA-01.
CORRIGIDO em 2026-08-05: a ficha descrevia o mecanismo errado, não uma ausência de mecanismo. Uma primeira medição (mesma data) consultou o serviço de armazenamento por objeto da plataforma-mãe e o achou vazio, para qualquer módulo, e fechou o cartão como aberto por bucket inexistente. Segunda medição, apontada por achado de outra frente e conferida por conta própria antes de aceitar: a plataforma-mãe NUNCA usou aquele serviço, de nenhum módulo, então a consulta dava zero por construção e continuaria dando zero para sempre, independente do ModularReports. O armazenamento de arquivo real da plataforma-mãe é outro mecanismo, baseado em sistema de arquivos sobre um volume dedicado do orquestrador de contêineres, o MESMO que os anexos de outras áreas do Hub já usam. Prova direta e não por leitura de código isolada: existem hoje, no volume real, arquivos de artefato do ModularReports gravados por upload de verdade (formatos de planilha, tamanhos entre 7 e 27 KB, timestamps de execução real), fora de qualquer ambiente de teste. A rota de leitura de asset do leitor público entrega a imagem por URL assinada por HMAC com expiração, que confere a assinatura antes de buscar o arquivo por um segundo salto interno autenticado por segredo compartilhado, e a leitura sempre recalcula o hash do conteúdo contra o hash esperado antes de servir. O gerador de PDF já foi confirmado entregando arquivo real, verificado visualmente página a página, em fatia anterior. Fecha por existir e ser alcançável, com o mecanismo real descrito abaixo.
Onde fica: sistema de arquivos da plataforma-mae, num volume dedicado do orquestrador de conteineres, num diretorio reservado ao modulo, subdividido por agencia e por relatorio (artefato cru) ou por agencia (asset persistente).
Especificação: Serviço: armazenamento de arquivo por sistema de arquivos, volume dedicado do orquestrador de contêineres | Papel no sistema: artefato cru (TTL curto) e assets de criativo/imagem (persistentes até o processo LGPD) | Mecanismo: escrita e leitura diretas no sistema de arquivos dentro de um diretório reservado ao módulo (o mesmo volume que outras áreas do Hub já usam para anexo), nunca um serviço de armazenamento por objeto separado | Estrutura de diretório: artefato cru em diretório do módulo, subpasta da agência, subpasta do relatório, arquivo por id do artefato mais extensão; asset persistente em diretório do módulo, subpasta assets, subpasta da agência, arquivo por id do asset mais extensão | Recursos: limite por arquivo de 50 MB | Integridade: o hash do conteúdo é recalculado na leitura e comparado ao hash esperado antes de qualquer entrega, nunca serve conteúdo cuja integridade não confere | Acesso do leitor público: por URL assinada com expiração curta (assinatura por segredo dedicado, verificada antes de qualquer busca), que aciona um segundo salto interno servidor a servidor autenticado por segredo compartilhado, nunca por caminho de arquivo cru exposto ao navegador | Escopo: por agência, nunca um caminho que misture agências | Health check e escalonamento e backup: herdam do volume do orquestrador de contêineres da plataforma-mãe.
Para que serve: o único processo de fato isolado do pipeline: container efêmero sem rede que faz o parse dos artefatos e devolve JSON saneado. A composição de IA NÃO é um worker de fila separado no V1: roda in-process (síncrona), reusando o gateway de IA da plataforma-mãe.
Relacionado com: RF-ING-003, RF-CMP-009, ADR-004, ADR-011; tarefas {F1-02}, {F4-03} do Roadmap.
Onde fica: container efêmero no orquestrador de contêineres da plataforma-mãe (docker Compose), disparado por parse, com deploy independente da fila de release do Hub.
Especificação: Serviço: sandbox de parse isolado | Papel no sistema: recebe os bytes do artefato, parseia e devolve JSON estruturado saneado; a composição de IA roda in-process fora deste container | Imagem/versão: contêiner efêmero próprio, morto ao fim do parse | Recursos: limites de CPU, memória e tempo, com kill por timeout | Portas/rede: SEM rede de saída (é onde mora o risco de exploit de arquivo: XXE, zip bomb, SVG com script, PDF malicioso) | Dependências: nenhuma de rede; recebe bytes e devolve JSON | Volumes/persistência: nenhuma, efêmero | Health check: o processo vive só durante o parse; timeout mata | Escalonamento: horizontal por demanda de parse | Backup: não aplicável (efêmero). Nota: um worker de fila assíncrono para o render é evolução, disparado pelo primeiro arquivo que estourar o tempo de request (RNF-PERF-002), não V1.
Para que serve: serve o snapshot publicado sob role só-SELECT, sem alcance a outros schemas.
Relacionado com: RF-REN-006, RF-REN-010, ADR-003; tarefas {F5-01} a {F5-05} do Roadmap.
Onde fica: serviço isolado, deploy independente da fila de release do Hub.
Especificação: Serviço: leitor público | Papel no sistema: resolve o token do link para o snapshot e o renderiza via package renderizador | Imagem/versão: serviço SSR próprio | Recursos: dimensionado pelo tráfego público esperado dos links | Portas/rede: exposto publicamente na rota do link, sem outras rotas administrativas | Dependências: role reports_reader com GRANT de SELECT apenas em report, snapshot e comment (não no schema inteiro, RN-011), package renderizador | Volumes/persistência: nenhuma além do cache HTTP (asset imutável por snapshot_id, RNF-SEG-005) | Health check: endpoint de saúde próprio, monitorado independente do Hub | Escalonamento: horizontal, cacheável | Backup: não aplicável (leitura derivada do banco).
Para que serve: ingere os eventos de audiência do link e escreve em link_event, separado do leitor público (que é só-SELECT); a coleta não bloqueia o caminho de leitura.
Relacionado com: RF-ANL-001, RF-ANL-003, RF-ANL-004; tarefa {F13-08} do Roadmap; entidade ART-ENT-06 (link_event), ART-INFRA-03 (o leitor emite, o ingestor grava).
Onde fica: container próprio no orquestrador de contêineres da plataforma-mãe, separado do leitor.
Especificação: Serviço: ingestor de eventos de analytics | Papel no sistema: recebe os eventos que o leitor emite e grava em link_event (view, referrer, is_agency_session, opaque_visitor_id) | Imagem/versão: contêiner próprio | Recursos: dimensionado pelo volume de acessos aos links | Portas/rede: canal de escrita interno em link_event; o leitor público segue sem permissão de escrita (RF-REN-006) | Dependências: schema reports (escrita só em link_event) | Volumes/persistência: os eventos ficam em link_event | Health check: liveness do processo | Escalonamento: horizontal, desacoplado do leitor por fila leve | Backup: coberto pelo backup do banco.
FECHADO por medição direta em produção em 2026-08-05, e a nota fica registrada porque contraria a expectativa inicial desta rodada. A expectativa era que nenhum mecanismo de disparo existisse. A medição encontrou um disparador real, ligado no arranque do serviço da plataforma-mãe junto dos demais disparadores periódicos do mesmo produto, e confirmado pelo próprio log de arranque anunciando o intervalo e o primeiro disparo. O mecanismo não depende de nenhuma extensão de agendamento do banco: é um temporizador do próprio processo, no mesmo molde dos demais disparadores periódicos já existentes na plataforma. Limite desta prova: confirmei o disparador VIVO e ligado, não uma volta completa (uma agenda vencida sendo de fato composta e fechada em produção), porque nenhuma agenda estava vencida no momento da medição. Se uma nova medição encontrar o mecanismo desligado ou falhando, este cartão reabre.
Para que serve: a rotina que, a cada dia, descobre quais agendas venceram ciclo e resolve o desfecho de cada uma: compor a edição, pular por pausa, ou fechar com o motivo da falha. É o que faz a agenda existir de verdade em vez de ser só uma linha configurada.
Relacionado com: RF-CMP-027, RF-CMP-029, RF-ADM-021, RN-017 itens 2 e 3; tarefas {F19-06} e {F19-08} do Roadmap; artefatos {ART-ENT-11}, {ART-ENT-12}, {ART-ENT-13} e {ART-NOT-02}.
Onde fica: rotina agendada do módulo, no mesmo serviço que já roda as tarefas de manutenção do Reports, na rede interna e sem porta pública.
Especificação: Nome: disparador de ciclo das agendas | Gatilho: agendamento diário em horário de baixa carga, nunca de hora em hora e nunca concentrado no mesmo minuto de outras rotinas | Entrada: as agendas cujo próximo vencimento, calculado da cadência e da âncora de calendário, é menor ou igual a hoje | Passos: 1 selecionar as agendas vencidas; 2 para cada uma, se o estado é paused, fechar o ciclo com skipped_paused e seguir; 3 se é active, abrir um report_job e compor pelo MESMO caminho da composição manual; 4 fechar o ciclo com o desfecho apurado; 5 notificar o responsável quando o desfecho não é generated | Idempotência: a chave do ciclo é (schedule_id, window), de modo que reprocessar o mesmo dia não gera segunda edição da mesma janela | Concorrência: uma agenda por vez, com trava por schedule_id, para que duas execuções sobrepostas não componham a mesma janela duas vezes | Tratamento de erro: falha de uma agenda nunca derruba o lote, cada uma fecha o próprio ciclo com o próprio desfecho, fail-closed por agenda | Retentativa: NENHUMA automática, por decisão (RF-CMP-029); o ciclo fica registrado e o operador decide | Observabilidade: cada execução registra quantas agendas venceram e a contagem por desfecho.
Interface
Para que serve: a navegação própria do Reports, isolada do menu lateral do Hub.
Relacionado com: RF-ADM-014; tarefa {F7-01} do Roadmap.
Onde fica: grupo de rotas lazy do chrome do Reports.
Especificação: Item de menu: Relatórios (lista), Clientes, Templates, Analytics, Identidade da agência, Usuários; para o Super Admin: Painel do Super Admin (entitlement, agências, limites) | Destino: rota própria do Reports por item | Visível para: Operador e Admin (itens operacionais); Admin e Super Admin (itens de configuração); Super Admin (painel do Super Admin) | Ícone: conjunto de ícones do design system da plataforma-mãe | Ordem: Relatórios, Clientes, Templates, Analytics, Identidade, Usuários | Sub-itens: Clientes tem sub-item Pasta do cliente | Condição de exibição: entitlement reports ligado na Company (RF-ADM-001) e capability do papel (ART-ACS-01).
Para que serve: as telas e páginas do ModularReports, uma por linha da tabela F.1 do DRS.
Relacionado com: todos os RFs do sistema; tarefas {F7} a {F16} do Roadmap; DRS bloco 6 (F.1, F.3), mockup navegável aprovado (17 telas, {M-01}).
Onde fica: chrome do operador, leitor público, painéis.
Especificação: cada superfície segue, tela a tela, a página correspondente do mockup navegável aprovado (REGRA-INV-2, ficha extensa referencia o mockup em vez de repetir campo a campo o layout já aprovado):
| Superfície | Público | Natureza | RFs principais |
|---|---|---|---|
| Chrome do operador | Operador, Admin | Aplicativo isolado, entrada pelo Hub e endereço direto | RF-ADM-014 |
| Lista de relatórios | Operador, Admin | Página inicial do módulo: 4 números, filtro por situação e por cliente, tabela por cliente; tela relatorios do mockup | RF-CUR-001, RF-ANL-006 |
| Wizard de composição | Operador | 4 passos (Ap.1) | RF-CMP-001 a 004 |
| Compositor | Operador | Curadoria de blocos, três colunas, com os painéis de identidade e de inserir elemento na coluna direita e o título do relatório editável em linha na barra superior | RF-CUR-001 a 013 |
| Modo apresentar | Operador | Tela cheia, rascunho ou publicado | RF-CUR-006 |
| Modal de publicação | Operador | Onde o link nasce (Ap.7) | RF-PUB-002 a 006 |
| Leitor público | Cliente final | Página estática isolada somente leitura | RF-REN-006, RF-REN-010 |
| Widget de comentário | Cliente final logado | Drawer lateral estilo documento colaborativo | RF-COM-001 a 006 |
| Painel de analytics | Admin | Agregado por agência | RF-ANL-006 |
| Painel do Super Admin | Super Admin | Entitlement, agências, limites, retenção | RF-ADM-001, RF-ADM-003 |
| Portal do cliente logado | Cliente final logado | Leitura agregada dos relatórios dos seus Accounts, opcional; tela cliente-painel | RF-REN-011 |
A Lista de relatórios entrou nesta tabela em 2026-08-18, e a ausência dela era o defeito {OP-01} do Roadmap. A superfície existe no mockup aprovado (relatorios.html) desde o início e estava construída como um estado vazio fixo dizendo "nenhum relatório publicado ainda" enquanto o painel de analytics da mesma agência contava quatro publicados. Ficou fora do inventário porque a ficha listava o "chrome do operador" e ninguém perguntou qual é a PRIMEIRA tela que ele abre — que é exatamente o que a REGRA-INV-1 cobra: existe E é alcançável.
Rota, elementos, estados e responsivo de cada superfície: ver o mockup navegável aprovado (link em {M-01} do Roadmap) e a jornada do papel correspondente (documento Jornadas dos Atores). A gestão de domínio custom da agência ({F14-02}) já é uma seção da tela de identidade da agência no mockup; o que falta ali é o estado "pendente de validação TXT" antes do "propagado", a incluir numa próxima iteração do mockup (ADR-015).
Para que serve: a superfície onde o operador cria, edita, pausa e retoma as agendas e lê o histórico de ciclos de cada uma, incluindo os que não geraram e por quê.
Relacionado com: RF-ADM-019 a RF-ADM-022, RF-CMP-029; tarefa {F19-09} do Roadmap; artefatos {ART-ENT-11}, {ART-ENT-12} e {ART-NAV-01} (a entrada no chrome).
Onde fica: chrome do operador do Reports, grupo de rotas próprio.
Especificação: Rota: /relatorios/agendas | Papel: operador e admin da agência | Estados: lista com agendas, lista vazia com chamada para criar a primeira, formulário de criação, formulário de edição, histórico de ciclos de uma agenda, e erro de recusa por agenda duplicada no mesmo par | Elementos: tabela de agendas com cliente, propósito, cadência, próximo vencimento, responsável e estado; ação de pausar e retomar em cada linha, com o rótulo refletindo o estado atual; painel lateral de histórico com um item por ciclo mostrando a janela e o desfecho em linguagem humana | Dados exibidos: só agendas da agência da sessão | Ações: criar, editar, pausar, retomar, abrir histórico | Navegação de saída: volta para a lista pela própria interface a partir de qualquer subtela, sem depender do voltar do navegador | Responsivo: a tabela vira lista empilhada abaixo do ponto de quebra de tablet | Acessibilidade: o estado da agenda é comunicado por texto e não apenas por cor.
Para que serve: é o portador de marca da AGÊNCIA na página que o cliente final abre, separado do portador do cliente, que é a abertura do relatório. Enquanto ele não existir, a página tem um portador só e ele carrega a marca da agência, o que impede a sobreposição de logo do cliente por relatório sem apagar a marca da agência (RN-020 item 4).
Relacionado com: RF-ADM-004, RF-CUR-004, RN-020; tarefa {F8-21} do Roadmap, da qual a Fatia B de {F8-08} depende; artefatos {ART-SCR-01} (linha do leitor público) e {ART-MEDIA-01} (o arquivo do logo servido).
Onde fica: topo de toda página do leitor público, acima da abertura do relatório; desenhada no protótipo navegável aprovado ({M-01}).
Especificação: Superfície: barra fixa no topo da página pública, presente em todo relatório publicado | Público: cliente final, sem login | Elementos: logo da agência, nome da agência e a linha de crédito declarando quem preparou o relatório | Origem dos dados: exclusivamente a identidade congelada no retrato publicado, nunca leitura ao vivo do cadastro, para não contradizer o congelamento de RN-002 e RN-020 item 3 | Estados: com logo da agência; sem logo, caso em que exibe o nome da agência em texto; e o estado de lacuna de identidade, que segue o aviso de RF-ADM-006 | Proporção: o logo entra na proporção natural, largura automática e ajuste por contenção, nunca esticado nem achatado, conferido pela proporção renderizada contra a proporção natural e não a olho | Contraste: o par de cores da barra respeita o mesmo mínimo de contraste do restante da página (RF-REN-007) | Responsivo: em tela estreita a barra mantém o logo e recolhe a linha de crédito | Acessibilidade: o logo tem texto alternativo nomeando a agência, e a barra não depende de cor para comunicar nada | Fora do escopo: a barra não exibe nada do cliente, não participa da precedência de cor do relatório e não substitui a capa do card de compartilhamento (Ap.7).
Análise
Para que serve: o admin acompanha o desempenho agregado dos relatórios da própria agência.
Relacionado com: RF-ANL-006; tarefa {F13-06} do Roadmap; ART-KPI-01, ART-ENT-06 (link_event).
Onde fica: chrome do Reports, seção Analytics.
Especificação: Objetivo: quanto os relatórios da agência estão sendo vistos e comentados | Quem acessa: Admin da agência (RLS por company_id) | Métricas/colunas: ver ART-KPI-01 | Filtros disponíveis: por cliente, por período de publicação | Agrupamentos: por cliente | Período/granularidade: acumulado desde a publicação, sem série temporal contínua (RF-BND-001) | Fonte de dados: link_event, report (production_minutes), comment | Ações por linha: abrir o relatório, abrir os comentários não lidos | Exportação: diferida (não especificada nesta fatia) | Atualização: consulta direta, sem cache de longa duração.
Para que serve: as métricas exibidas no painel de analytics por agência.
Relacionado com: RF-ANL-002, RF-ANL-003, RF-ANL-005, RF-ADM-015; tarefas {F13-02}, {F13-03}, {F13-05}, {F13-07}.
Onde fica: painel de analytics por agência (ART-DASH-01).
Especificação:
| Nome | Definição | Fórmula | Unidade | Granularidade | Fonte |
|---|---|---|---|---|---|
| Visualizações | Acessos ao link vindos de fora da plataforma, excluindo qualquer navegador com sessão da plataforma (de qualquer agência) e o modo apresentar | Contagem de link_event com event_type=view e is_agency_session=false, sempre pela fonte única v_honest_view_event | Contagem | Por relatório | link_event |
| Último acesso | Data e hora do acesso mais recente ao link | MAX(created_at) de link_event filtrado como acima | Timestamp | Por relatório | link_event |
| Origem por referrer | Distribuição de acessos por referrer, ou "desconhecida" | Agrupamento de link_event.referrer | Contagem por origem | Por relatório | link_event |
| Duração de produção | Tempo entre início da composição e publicação | published_at - created_at (minutos) do report | Minutos | Por relatório | report.production_minutes |
Sem meta ou baseline formal nesta fatia; alimenta o critério de adoção (DRS A.7).
Governança
FECHADO em 2026-08-18, nascido da medição do {F8-58} do Roadmap. Diferente dos demais artefatos
deste documento, esta guarda não é do módulo de relatórios: nasceu aqui porque foi a obra do {F8-53}
(módulo reports) que, contando os avisos do próprio módulo, mediu o Hub inteiro de passagem e achou o
padrão repetido em outros produtores. Fica catalogada no Inventário do Reports por proveniência, e vale
para os 20 produtores de aviso do Hub.
Para que serve: impedir que qualquer produtor do Hub publique um aviso (messages.notifications)
cujo link não casa com nenhuma rota declarada no front, o defeito que fazia o clique levar ao painel
inicial em silêncio.
Relacionado com: tarefas {F8-53} e {F8-58} do Roadmap (medição e generalização); sucessor nomeado
{F8-62} (o achado de que, no módulo control, o mesmo campo link também é chave de deduplicação, e
por isso fica de fora da correção em massa desta guarda); artefato irmão {ART-AUD-02} (mesma disciplina
de fixture de controle negativo).
Onde fica: apps/api/src/lib/notifications.ts (publishNotification, ponto único de escrita, 20
importadores, zero insert direto em produção) e apps/api/src/lib/web-routes.ts (espelho de rotas do
front, sem re-export); o casador matchesDeclaredRoute vive em notif-link-scan.ts.
Especificação: `Gatilho: toda chamada a publishNotification | Verificação: o link é conferido contra
o espelho de rotas declaradas (web-routes.ts) via matchesDeclaredRoute | Modo de falha: fail closed,
publicação recusada quando o link não casa e não há isenção nomeada válida | Isenções: 7, nomeadas por
PREFIXO, só para o módulo control, nunca por módulo inteiro; cada isenção carrega motivo escrito, dono e
cartão de saída ({F8-62}); publicar sob isenção emite o log "[notif-link][isento]"; isenção MORTA
(prefixo que deixou de ser usado) reprova a guarda estática | Cobertura medida no fechamento: dry-run com
o balde "lançaria" vazio antes de ligar a guarda; guarda estática 90 aprovações e 0 reprovações
indevidas; par de casos em execução real 26 aprovados e 0 reprovados (6 endereços mortos recusados sem
gravar, 27 legítimos publicando normalmente); reprovação de isenção morta provada por quebra proposital
| Destinos mortos corrigidos no mesmo fechamento: /canais-email/threads?conv=<id> (a página do Hub passou
a ler ?conv), /empresas?company=<id>&suspension=<t> (LIKE do dedup cobrindo os dois formatos por uma
janela), /agencia | Dado histórico: nada foi apagado no banco, os avisos publicados com endereço morto
antes da guarda seguem medidos e relatados, não removidos.`
Para que serve: trilha imutável das ações relevantes do Reports (publicar, republicar, revogar, encerrar cliente).
Relacionado com: RF-ADM-012; tarefa {F6-05} do Roadmap; log de auditoria existente do Hub (hub.audit_log).
Onde fica: hub.audit_log, eventos com event_type prefixado reports..
Especificação: Eventos registrados: reports.report.published, reports.report.republished, reports.link.revoked, reports.client.offboarded (lista mínima, extensível) | Campos de cada evento: ator (user/contact id), ação (event_type), alvo (report_id ou account_id), valor anterior e valor novo (old_value/new_value, quando altera estado; a republicação usa isso para derivar o log humano de RF-PUB-010), timestamp | Imutabilidade: append-only POR RLS (as policies só permitem INSERT e SELECT; não há UPDATE nem DELETE por política; o service_role ainda alcança o storage, então é append-only por RLS, não imutável a nível físico) | Dado pessoal: NENHUM registro contém dado pessoal cru, só evento e identificadores (RN-007); a pseudonimização LGPD acontece nas tabelas de origem, nunca mutando o log | Retenção: herda a política de retenção do log do Hub, exceto onde a retenção específica do Reports (bloco 11) for mais curta | Quem consulta: Admin e Super Admin | Filtros disponíveis: por agência, por tipo de evento, por período | Onde fica armazenado: hub.audit_log, ponto único de escrita via o helper de auditoria do Hub (RF-ADM-012).
Para que serve: avisa o autor do comentário quando o operador marca como resolvido, fechando o loop.
Relacionado com: RF-COM-005; tarefa {F12-05} do Roadmap; ART-ENT-05 (comment).
Onde fica: canal de notificação existente do Hub (Central de Notificações), endereçado ao Contact autor.
Especificação: Evento que gera: comment.resolved_at preenchido | Destinatário: author_contact_id do comentário | Texto: "Seu comentário no relatório <título> foi resolvido" | Canal in-app: Central de Notificações do Hub | Ação ao clicar: abre o link público do relatório ancorado no bloco do comentário | Tempo real ou polling: herda o mecanismo de notificação do Hub | Quando marca como lida: ao abrir a notificação | Agrupamento: por relatório, se houver mais de um comentário resolvido no mesmo lote.
Documento e mídia
Para que serve: versão em PDF do relatório publicado, gerada sob demanda.
Relacionado com: RF-REN-008, RF-REN-009; tarefas {F15-01}, {F15-02} do Roadmap; ART-INFRA-03 (leitor), package renderizador ({F3-02}).
Onde fica: gerado sob demanda a partir do snapshot; não persiste como arquivo permanente além do cache de geração.
Especificação: Finalidade: levar o relatório para fora do link (anexo de email, impressão) | Gatilho de geração: ação "baixar PDF" no leitor público ou no chrome do operador | Dados/variáveis que preenche: o mesmo payload do snapshot, renderizado pelo package renderizador | Layout: mesma ordem de blocos do snapshot, tema claro, contraste AA | Idioma: o idioma do relatório publicado | Assinatura eletrônica: não | Onde fica armazenado: não persiste, gerado por requisição a partir do snapshot; se o gerador da plataforma-mãe estiver indisponível, a opção não é oferecida (RF-REN-008) | Versão/numeração: nenhuma, reflete sempre o snapshot vigente | Exigência regulatória: nenhuma.
Para que serve: fonte única de cores, logo, fontes e tom de voz do cliente, usada pela composição e pelo branding da página.
Relacionado com: RF-ADM-005, RF-ADM-006, RF-CMP-013; tarefas {F16-01}, {F16-02} do Roadmap; DRS Ap.6 (ficha completa).
Onde fica: tabela existente hub.account_identidade_visual; o Reports lê e popula, não cria tabela própria e não exige migration de ALTER (a tabela tem um campo de dados livre jsonb que já comporta logos, cores, fontes, tom de voz e restrições; a forma é validada na camada da rota).
Especificação: ficha extensa, referenciada no Apêndice 6 do DRS ("Ficha de identidade visual do cliente"), REGRA-INV-2. Resumo dos campos: Nome do campo: logos[] | Tipo: array de referência de asset (dentro do jsonb data) | Opções: N/A | Valor default: vazio, bloqueia ou usa default da agência (RF-ADM-006) | Obrigatório: não, com bloqueio condicional | Quem escreve: Admin, Operador | Quem lê: pipeline de composição, package renderizador | Regra associada: RF-ADM-006. Sem ALTER: o jsonb livre já comporta todos os campos. Campos completos (cores, fontes, tom_de_voz, restrições): Ap.6 do DRS.
Para que serve: dizer se o negócio daquele cliente é sazonal, o que muda a base de comparação primária de todos os relatórios dele do período imediatamente anterior para o mesmo período do ano anterior. Existe como atributo do CLIENTE, e não como escolha por relatório, porque escolher a base relatório a relatório é como o viés entra na entrega, e viés não aparece dentro de um relatório: aparece na comparação entre relatórios, que é quando ninguém está olhando.
Relacionado com: RF-ADM-018, RN-016, RF-CMP-024; tarefa {F18-09} do Roadmap; artefatos {ART-FLD-01} (mesma ficha do cliente) e {ART-ENT-01} (report, a base escolhida é copiada para o snapshot no publish).
Onde fica: tabela account_profile do schema reports ({ART-ENT-14}), editável no painel de clientes do Reports. Consolidado ali em 2026-08-03: a sazonalidade estava escrita para viver num campo livre da ficha de identidade do Hub, e passou a dividir tabela com o modelo de venda porque os dois são a mesma coisa (atributo do cliente que muda a leitura dos relatórios dele) e cada um num lugar diferente é como o segundo vira campo solto. Nada foi construído no lugar antigo, então a consolidação não desfaz trabalho: {F18-09} nasce já apontando para cá.
Especificação: Nome do campo: is_seasonal | Tipo: booleano | Opções: N/A | Valor default: falso | Obrigatório: sim, com default | Quem escreve: Admin da agência | Quem lê: pipeline de composição, no ato de resolver a base de comparação | Regra associada: RF-ADM-018, RN-016. Segundo campo, dependente: Nome do campo: peak_month | Tipo: inteiro | Opções: 1 a 12 | Valor default: nulo | Obrigatório: não, e só faz sentido com is_seasonal verdadeiro | Quem escreve: Admin da agência | Quem lê: pipeline de composição | Regra associada: RF-ADM-018. Comportamento de leitura: com sazonal verdadeiro, a base primária é o mesmo período do ano anterior; com falso, é o período imediatamente anterior. Nenhuma tela do wizard ou do compositor oferece essa escolha por relatório. Mudar o atributo NÃO altera relatório já publicado, porque a base foi copiada para o snapshot imutável (RF-PUB-001).
Para que serve: provar que cada guarda do sistema continua guardando. Uma trava sem um caso que ela DEVE reprovar não é trava, é comentário: ela passa verde para sempre, inclusive no dia em que parar de funcionar. A inversão é o valor do artefato: no dia em que a suíte passar verde com uma dessas fixtures, isso significa que a trava morreu, e a integração contínua grita.
Relacionado com: RNF-MANUT-003, e por construção todas as guardas do DRS (RF-CMP-016, RF-CMP-018, RF-CMP-021, RF-CMP-022, RF-CMP-024, RF-CMP-025, RF-CMP-026, RF-REN-013, RF-REN-014, RF-REN-015, RF-CUR-009); tarefa {F18-11} do Roadmap; teste {TA-101}.
Onde fica: pasta de fixtures do projeto, executada pela suíte da integração contínua junto com os testes normais.
Especificação: cada fixture é um trio { entrada, guarda alvo, expectativa: REPROVA }, e a suíte falha quando a expectativa não se cumpre. Fixtures nominais iniciais, uma por guarda: trimestre-parcial-contra-trimestre-cheio (janela de 31 dias contra 92, guarda RF-CMP-021, entrada derivada do caso real que produziu a afirmação de queda de 71 por cento); alcance-somado-entre-tres-janelas (RF-CMP-022); numero-da-campanha-atribuido-ao-criativo (a frase "o criativo em vídeo alcançou 167.364 impressões em julho" com ponto de dado cuja tupla diz entidade igual a campanha, RF-CMP-025); causal-sem-evidencia-designada (RF-CMP-026); line-chart-de-dois-pontos (min_periods 3 com 2 períodos, RF-REN-014); nao-medido-gravado-como-zero (RF-REN-015); dois-hero-no-mesmo-documento (RF-REN-013); dado-mudado-com-leitura-identica (RF-CMP-016); comparativo-sem-base-na-frase (RF-CMP-024); payload-com-campo-excluido (y_format, cor hexadecimal em fatia de donut, body_html, Ap.3). Conferência de completude: um passo da integração contínua enumera as guardas declaradas no DRS e falha se alguma não tiver fixture, o que bloqueia o merge de guarda nova sem a sua fixture. Prova de que a fixture serve: desligar a guarda deixa a suíte VERMELHA por causa dela; uma fixture que não fica vermelha ao desligar a guarda é uma fixture inútil e é tratada como defeito.
FECHADO em 2026-08-05, corrigido junto com {ART-INFRA-01}: mesma causa, mesmo mecanismo real. A rota de upload de asset (recebe a imagem, valida tipo e tamanho, calcula o hash e grava no volume por agência) está registrada e chamável, usando a MESMA função de gravação já comprovada pelos arquivos reais de artefato no volume. A rota de leitura (URL assinada com expiração, salto interno autenticado, hash reconferido) também está registrada e é a que o leitor público usa para servir imagem de bloco de mídia. Nenhum asset de criativo real foi enviado em produção até esta medição (o caminho ainda não foi usado, só provado alcançável pelo código e pelo mecanismo irmão já em uso real); se isso mudar, reabrir com o motivo. A capa gerada por código do card de compartilhamento (RF-PUB-006, formato vetorial servido sem senha) não depende deste mecanismo e funciona hoje de forma independente.
Para que serve: as imagens de anúncio (criativos) inseridas como blocos de mídia no relatório, e a capa do card de compartilhamento.
Relacionado com: RF-ING-005, RF-CUR-003, RF-PUB-006; tarefas {F2-05}, {F8-07} do Roadmap; ART-INFRA-01 (armazenamento de arquivo).
Onde fica: armazenamento de arquivo do módulo (sistema de arquivos sobre volume dedicado), pasta de assets por agência.
Especificação: Tipo de asset: imagem (criativo de anúncio, logo do cliente, capa de compartilhamento) | Finalidade: bloco de mídia do relatório (RF-CUR-003), capa og:image do card (RF-PUB-006) | Dimensões/formato: conforme o uso (criativo na proporção original, capa em formato de card social) | Variações: nenhuma variação de tema exigida (a imagem é do cliente, não do chrome) | Fonte: enviada pelo operador como artefato ou selecionada da ficha de identidade do cliente | Onde fica armazenado: armazenamento de arquivo do módulo (sistema de arquivos sobre volume dedicado), referenciado por asset_id e sha256, nunca por URL crua ou base64 no payload (RF-ING-005) | Legenda: campo caption do bloco, texto editável pelo operador, sob a disciplina de número como qualquer prosa editável (métrica citada resolve por {{dp:id}}, data e ordinal seguem livres, RF-CUR-011) | Autoria do bloco: o bloco de mídia inserido pelo operador carrega o valor reservado operator-inserted em tags e sobrevive a qualquer reescrita do conjunto de blocos do rascunho (RF-CUR-010).
Para que serve: dizer se o negócio daquele cliente é de Venda Consultiva ou de Venda Direta. Tem exatamente um efeito no sistema: trocar o vocabulário das quatro etapas do funil canônico. A estrutura do funil, a ordem das etapas, a tripla de números por etapa e o conjunto de métricas da camada de resultado são idênticos nos dois modelos.
Relacionado com: RF-ADM-023, RF-REN-016, RN-018, ADR-022, Ap.10.1; tarefas {F20-02} e {F20-03} do Roadmap; artefatos {ART-ENT-14} (a tabela onde o campo vive) e {ART-FLD-02} (o vizinho de mesma natureza).
Onde fica: tabela account_profile do schema reports, editável no painel de clientes do Reports pelo admin da agência.
Especificação: Nome do campo: sale_model | Tipo: enumerado de 2 | Opções: consultiva (Venda Consultiva), direta (Venda Direta) | Valor default: nulo | Obrigatório: NÃO, e nulo é estado válido e não pendência: cliente sem modelo declarado usa o funil livre de sempre | Quem escreve: Admin da agência | Quem lê: pipeline de composição, no ato de montar o funil canônico | Regra associada: RF-ADM-023, RN-018. Comportamento de leitura: com o campo preenchido, o funil daquele relatório é o canônico de quatro etapas com os rótulos do Ap.10.1; com o campo nulo, o funil é o livre, com o número de etapas e os rótulos que o operador montar, exatamente como funciona hoje. O valor é copiado para o bloco no ato da composição, então trocar o modelo do cliente depois NÃO altera rótulo de relatório já publicado (RF-PUB-001, RN-018 item 6). Um valor fora dos dois é recusado pelo banco, e não apenas pela interface.
Para que serve: avisar o operador responsável quando um ciclo de uma agenda ativa venceu e não produziu relatório, com o motivo. Existe porque o modo de falhar mais caro de um agendamento é o silêncio: a operação passa a confiar que o sistema está gerando enquanto ele parou, e ninguém reporta o que não aparece.
Relacionado com: RF-CMP-029, RN-017 item 3; tarefa {F19-08} do Roadmap; artefatos {ART-ENT-12} (o ciclo que gerou o aviso), {ART-JOB-01} (quem dispara) e {ART-NOT-01} (o vizinho que usa o mesmo canal).
Onde fica: Central de Notificações interna da plataforma-mãe, nunca canal novo.
Especificação: Nome: ciclo de agenda sem edição | Canal: Central de Notificações interna da plataforma-mãe, publicada pela mesma função de sistema que os demais avisos do módulo usam | Gatilho: fechamento de um ciclo com desfecho diferente de generated | Destinatário: o responsável declarado na agenda, e o admin da agência | Conteúdo: o cliente, o propósito, a janela do ciclo e o motivo em linguagem humana, um dos quatro nominais (não havia artefato do período, o parse do artefato falhou, a cadeia de inteligência artificial estava indisponível, uma guarda reprovou a composição) | Ação oferecida: abrir a agenda no histórico de ciclos | Agrupamento: um aviso por ciclo, nunca um por tentativa, porque não existe retentativa automática | Tipo e origem: alerta de sistema com o módulo declarado como origem | Silenciável: não pelo operador; o admin da agência pode desligar a categoria inteira nos parâmetros da agência.
Para que serve: permitir provar COMPORTAMENTO DE TELA por teste automatizado, e não só por smoke visual. A interface do operador não tinha nenhum teste, então toda cláusula de critério que dependia do que a tela faz (bloquear avanço, exibir lacuna, recusar campo fora da lista) só podia ser verificada a olho, uma vez, por quem estivesse olhando.
Relacionado com: os cartões Alfa de comportamento de tela ({TA-28}, {TA-46}, {TA-54}, {TA-82} fechados por ele; {TA-26}, {TA-42} e {TA-15} com cobertura parcial declarada); RNF-MANUT-003.
Onde fica: aplicação web do operador, no mesmo repositório da interface.
Especificação: Nome: ambiente de teste de componente da interface | Tipo: ferramental de desenvolvimento | Escopo: ESCOPADO por decisão, para não colidir com os scripts de teste autoexecutáveis que já existiam no repositório (rodar tudo sob o mesmo comando faria os autoexecutáveis dispararem como se fossem testes) | Estado medido em 2026-08-03: 2 arquivos de teste de componente com 11 casos no total, mais 1 arquivo de teste de biblioteca preexistente | Regra de nascimento: cada caso nasce com controle negativo provado individualmente (quebrar a linha que ele protege, ver vermelho, restaurar), mesma disciplina da RNF-MANUT-003 | Quem usa: quem implementa a interface do operador.
Para que serve: achar campo que a API devolve e nenhum consumidor lê, que é o sintoma inverso do defeito mais comum desta sessão (peça pronta e não acionada). Rodável sob demanda.
Relacionado com: RNF-MANUT-006 (contrato público verificado por máquina, a irmã que roda como trava); {F20-09}, cujo defeito era exatamente um aviso devolvido pela API e não consumido pelo painel.
Onde fica: ferramental de manutenção do repositório da interface.
Especificação: Nome: varredura de campo de resposta não consumido | Tipo: ferramental de manutenção | NÃO É GATE, por decisão medida: a taxa de falso positivo foi medida em 85,7 por cento, e gate barulhento morre e deixa sensação FALSA de cobertura, que é pior do que não ter gate | Modo de operação: CATRACA. O que é legítimo se anota como aceito, e a rodada seguinte mostra apenas o que é novo | Efeito medido em 2026-08-03: a lista caiu de 21 para 1 | LIMITE CONHECIDO, declarado como limite e NÃO como cobertura: existe uma classe de campo fora do alcance do método (o retorno cujo tipo é declarado no próprio uso), e há indício de mais casos nela sem quantificação confiável. A varredura não cobre essa classe, e dizer que cobre seria pior do que não ter a ferramenta | Quem usa: quem mantém a interface, sob demanda.
Blocos do renderizador
Espelha o Ap.3 do DRS (catálogo de tipos de bloco) e o RF-REN-012 (cobertura nominal). Um tipo de bloco só existe de verdade quando é componente MAIS entrada no registro declarativo: componente sem entrada no registro cai no caminho de tipo desconhecido e some da página do cliente.
Para que serve: são as peças com que todo relatório é montado; cada tipo é um componente do package renderizador mais uma entrada no registro declarativo, e é o registro que faz o leitor, o preview e o PDF desenharem a mesma coisa a partir do mesmo documento.
Relacionado com: RF-REN-001, RF-REN-002, RF-REN-005, RF-REN-012, Ap.2 e Ap.3 do DRS; tarefas {F2-01} a {F2-03}, {F3-01} a {F3-04}, {F17-01} a {F17-04}, {F17-09}, {F17-10} e {F18-16} do Roadmap; artefatos {ART-ENT-02} (report_draft.blocks), {ART-ENT-03} (snapshot.payload) e {ART-TPL-01} a {ART-TPL-03} (os modelos declaram sequências desses tipos).
Onde fica: registro declarativo do package renderizador, consumido pelo leitor público, pelo compositor e pelo gerador de PDF.
Especificação: a ficha de props de cada tipo (campo, tipo, se carrega dp, se é editável, cap de verbosidade) vive no Ap.3 do DRS, que é a fonte única (REGRA-INV-2); a conferência de cobertura de RF-REN-012 compara o registro contra a lista nominal do Ap.3, nunca contra uma lista escrita à parte. Lista nominal e fechada dos 16, com o estado medido em 2026-08-02: hero (no registro; SINGULAR, no máximo um por documento, RF-REN-013, {F17-10}), kpis (no registro), funnel_simple (no registro), stat_cards (no registro), video_retention (no registro), rankings (no registro), data_table (no registro), insights (no registro), gap (no registro), comparison (no registro, aposta sem uso real confirmado, com condição de morte em ADR-018), media (no registro, necessidade confirmada por contorno), section_intro (a construir, {F17-01}, divisor puro repetível, sem period e sem source), reliability (a construir, {F17-09}, DERIVADO por código e não editável, RF-CMP-023), line_chart (a construir, {F17-02}), donut (a construir, {F17-03}), action_plan (a construir, {F17-04}). Placar: 11 no registro mais 5 a construir fecham em 16. Grau de confirmação por uso de cada tipo: tabela do Ap.3.
Três campos do renderizador irmão barrados por schema ({F18-16}, tabela de exclusões do Ap.3), porque sem barreira eles voltam na primeira vez que alguém copiar um payload de lá: line_chart.y_format (o formato vem só do dp, e duas autoridades de formato para o mesmo número geram o defeito de coerência de rótulo de RF-CMP-025), cor hexadecimal livre em donut.slices[] (existe accent_index sobre a rampa de acento da identidade do cliente, com o clamp de contraste AA), e body_html em qualquer bloco narrativo (existe body_md com allowlist; HTML cru anula o saneamento em todas as superfícies de uma vez). A mensagem de recusa nomeia o campo substituto.
Ressalva medida em 2026-08-05, sobre o parágrafo anterior: a barreira por schema dos três campos excluídos (assertBlockPayloadContractV3, {F18-16}) segue SEM ponto de chamada em produção, confirmado direto no código hoje publicado. Os 16 tipos existem e renderizam; a barreira específica desses três campos não está ligada, e o cartão {F18-16} do Roadmap segue aberto por este motivo exato. Isto não reabre {ART-BLK-01} (o registro de tipos, que é o que o cartão promete, existe e funciona) mas fica registrado aqui porque é a mesma peça que o parágrafo acima descreve.
Campos de guarda que todo tipo aplicável carrega: min_periods (histórico mínimo, abaixo do qual a seção se oculta e vira lacuna declarada, RF-REN-014) e, nos blocos comparativos, base obrigatório e visível dentro da frase (RF-CMP-024). Nulo em campo numérico significa NÃO MEDIDO e nunca zero, com render interrompido, no vocabulário único de RF-REN-015.
Modelos (templates)
Espelha o Ap.9 do DRS (catálogo de modelos) e a RN-013 (escopo Global e individual). Dois nascem Globais e um nasce individual, e a diferença é a contagem que sustenta cada um, não uma preferência: Diagnóstico de Herança tem N igual a 4 e Fechamento de Campanha tem N igual a 2, confirmados por relatórios reais no formato v2, e vão para Global (company_idnulo,is_globalverdadeiro, sem despromoção possível, RN-013 item 4, retiráveis apenas por exclusão); Fechamento Periódico Multi-Vertical tem N igual a 1, e essa amostra está em formato anterior ao v3, então nasce individual da agência de origem (is_globalfalso,company_idpreenchido). Semear no Global uma sequência inferida de uma amostra só apresentaria uma hipótese a todas as agências como se fosse o jeito certo de fazer. A ficha completa de cada um (sequência de blocos, regra própria, amostra que o sustenta) vive no Ap.9 do DRS (REGRA-INV-2); o campo Especificação aqui traz o que basta para provisionar a linha.
Para que serve: o gabarito do relatório de entrada de uma conta assumida, quando a agência precisa ler o histórico herdado antes de operar.
Relacionado com: RF-ADM-009, RF-ADM-017, RN-013, DRS Ap.9 (modelo 1); tarefa {F17-08} do Roadmap; artefatos {ART-ENT-07} (template) e {ART-BLK-01} (usa section_intro, reliability, line_chart, donut e action_plan, que dependem de {F17-01} a {F17-04} e {F17-09}).
Onde fica: tabela reports.template, schema reports, visível na biblioteca de toda agência com o módulo ligado.
Especificação: name: Diagnóstico de Herança | type (propósito): pesquisa | company_id: nulo (semeado pela operação, sem despromoção) | is_global: true | cadência típica da instância: pontual | default_blocks.blocks (na ordem): section_intro, reliability, stat_cards, funnel_simple, line_chart, video_retention, data_table, donut, insights (em série), rankings, data_table, action_plan, gap | default_blocks.direction_profile: nulo (o diagnóstico não herda direção de um relatório anterior) | props de todos os blocos: vazio (RF-ADM-010, um modelo nunca carrega número nem texto de cliente) | Regra própria: o segundo bloco é o tipo reliability, DERIVADO por código a partir das fontes que alimentaram o relatório, das lacunas e da base da âncora (RF-CMP-023), e não editável em nenhuma superfície; nos 4 relatórios reais esse parágrafo era digitado à mão, e por isso variava de completude entre eles | Amostra que o sustenta: 4 relatórios reais entregues, um por conta assumida, confirmados por leitura da lista de seções de cada um | Ficha completa: DRS Ap.9, modelo 1.
Para que serve: o gabarito do fechamento de uma campanha específica, quando não existe histórico do cliente contra o qual comparar.
Relacionado com: RF-ADM-009, RF-ADM-017, RN-013, DRS Ap.9 (modelo 2); tarefa {F17-08} do Roadmap; artefatos {ART-ENT-07} e {ART-BLK-01} (usa apenas tipos que já estão no registro).
Onde fica: tabela reports.template, schema reports, visível na biblioteca de toda agência com o módulo ligado.
Especificação: name: Fechamento de Campanha | type (propósito): campanha | company_id: nulo (semeado, sem despromoção) | is_global: true | cadência típica da instância: pontual | default_blocks.blocks (na ordem): funnel_simple, stat_cards, video_retention, rankings, data_table, insights, gap | default_blocks.direction_profile: nulo | props de todos os blocos: vazio | Regra própria: nasce sem bloco de régua e sem bloco de série no tempo (line_chart, donut) por motivo de operação e não por esquecimento, porque nos relatórios reais que o sustentam não havia histórico do cliente para comparar; quando houver histórico, o operador acrescenta os blocos ou usa o modelo Diagnóstico de Herança | Amostra que o sustenta: 2 relatórios reais do mesmo cliente, duas campanhas distintas, com sequência de blocos idêntica entre si | Ficha completa: DRS Ap.9, modelo 2.
Para que serve: o gabarito proposto do fechamento de um período de operação que cobre várias verticais do mesmo cliente. Este artefato é HIPÓTESE, não padrão: uma única amostra o sustenta, e essa amostra está em formato anterior ao contrato de bloco v3, ou seja, nem sequer traz uma lista de seções tipadas de onde extrair a sequência. A sequência abaixo é leitura editorial traduzida para os tipos do Ap.3, não transcrição de dado.
Relacionado com: RF-ADM-009, RF-ADM-017, RN-013, DRS Ap.9 (modelo 3); tarefa {F17-08} do Roadmap; artefatos {ART-ENT-07} e {ART-BLK-01} (usa section_intro, reliability e line_chart, que dependem de {F17-01}, {F17-02} e {F17-09}).
Onde fica: tabela reports.template, schema reports, visível apenas na biblioteca da agência de origem, com a marca de hipótese na descrição exibida ao operador.
Especificação: name: Fechamento Periódico Multi-Vertical | type (propósito): fechamento | company_id: a agência de origem (PREENCHIDO, não nulo) | is_global: false | cadência típica da instância: mensal | default_blocks.blocks (sequência PROPOSTA, na ordem): section_intro, reliability, stat_cards, line_chart, data_table (uma por vertical), insights (em série), gap | default_blocks.direction_profile: nulo | props de todos os blocos: vazio | Estado: HIPÓTESE, e por isso INDIVIDUAL e não Global. A descrição exibida ao operador declara isso com a amostra, e não apresenta o modelo como o jeito certo de fazer | Amostra que o sustenta: 1 relatório real, em formato anterior ao contrato v3 | Condição de promoção a Global: quando existirem 2 ou mais relatórios reais deste propósito no contrato v3, a sequência é reextraída do dado e o Super Admin promove sob demanda (RN-013, RF-ADM-016), preservando a agência de origem | Ficha completa: DRS Ap.9, modelo 3.
Parâmetros de configuração
**REMEDIDO em 2026-08-08, depois das cinco fatias do {F4-07}: 15 de 20 têm hoje caminho de edição
comprovado por uma pessoa (eram 4 em 2026-08-05). O cartão continua ABERTO porque o número não
fecha com os 20.** A medição foi feita de novo, mesma disciplina da rodada anterior: cada um dos 20
conferido por leitura do código real e dos três níveis (o catálogo declarativo
modularreports-dev/src/config-params.mjs, a rota HTTP com guarda de permissão em
apps/api/src/routes/reports.ts, e o formulário que a tela gera a partir do catálogo em
ReportsBrandingPage.tsx/ReportsClientFilePage.tsx), não por busca textual nem por dedução a
partir do texto do cartão do Roadmap. Confirmado contra o HEAD publicado em produção
(modularhub-dev e modularreports-dev, merge da Fatia E, 2026-08-08, container mhub-api
reconstruído na mesma janela).
Os 15 com caminho de edição comprovado: os 4 que já funcionavam (retenção de snapshots, comentários e analytics; identidade padrão da agência, dois campos separados; tipos de relatório; a promoção de um tipo a global, restrita ao Super Admin) mais os 10 que as Fatias A, B, C e E do {F4-07} ligaram ao catálogo declarativo e ao formulário genérico por forma (quota de entrada; TTL do artefato cru; tolerância de largura de janela; desfecho da comparação parcial; métricas não somáveis entre janelas; limiar de materialidade da variação; histórico mínimo por tipo de bloco; allowlist de fontes de benchmark; mapa objetivo para métrica âncora; rate-limit do link público, escopo global puro, Super Admin) mais 1 que a Fatia D ligou por fora deste catálogo, na ficha do cliente (base sazonal da cadência mensal, account_profile.is_seasonal, tela em ReportsClientFilePage.tsx).
Os 5 que faltam, e por quê:
- Teto de tempo por bloco e piso e teto do tempo de documento (2): a função que os aplica não é
chamada em lugar nenhum do caminho real; viraram o cartão {F8-16} do Roadmap, que mede o sintoma antes de construir tela para um limite que hoje não vale nada.
- Teto de verbosidade por bloco (1): mesma classe, guarda que ninguém aciona; diferido junto dos
dois acima, no mesmo {F8-16}.
- Guardas com override permitido (1): não deve ganhar painel, porque o próprio requisito que a
ficha cita (RF-CUR-009) declara essa lista escrita em código e julgada por desenho, não em runtime. O defeito aqui é a linha da tabela abaixo prometendo um painel, não a ausência de tela.
- Limiar de alerta de excesso de ressalvas (1): feature que não existe no produto hoje. Não é
parâmetro sem tela, é requisito ainda não construído; sai deste cartão quando alguém decidir construí-lo.
Duas linhas da tabela abaixo prometem painel que não deve existir, e a correção fica registrada aqui até a tabela ser reescrita: "Guardas com override permitido" é escrita em código por desenho, e "Limiar de alerta de excesso de ressalvas" é feature inexistente, não parâmetro pendente de tela.
Fila: os 3 diferidos vivem em {F8-16} do Roadmap; os 2 de correção de ficha não têm tarefa de construção, porque não devem ganhar uma. Este cartão só fecha ([x]) quando os 20 tiverem caminho de edição comprovado, o que hoje depende de {F8-16} fechar e de uma decisão de escopo sobre os 2 que a tabela promete incorretamente (corrigir a ficha ali é o suficiente para esses dois contarem). Para que serve: todo valor operacional que o admin pode querer mudar sem migration ou deploy (DRS bloco 11). Relacionado com: RF-ING-002, RF-ING-004, RF-ADM-011, RF-CMP-014, RF-ING-006, RF-ADM-006, RF-CMP-015, RF-ADM-009, RF-PUB-002, RF-CMP-016, RF-REN-014, RF-CUR-009, RNF-PERF-002, RN-012; tarefas {F4-02}, {F4-04}, {F16-05}, {F1-10}, {F4-05}, {F16-02}, {F1-11}, {F10-01}, {F9-02}, {F1-18}, {F18-05}, {F18-07}, {F18-10}, {F18-14}. Onde fica: painel do admin de agência (e painel do Super Admin para os de escopo global). Especificação:
| Nome do parâmetro | O que controla | Tipo | Valor default | Faixa/opções | Quem edita | Onde edita | Efeito ao mudar | Auditado | RN/RF |
|---|---|---|---|---|---|---|---|---|---|
| Quota de entrada | Nº de artefatos, MB totais, páginas de PDF por composição | Tabela (3 números) | A definir na Fatia 4 | Inteiro positivo | Admin | Painel de configuração da agência | Imediato, próxima composição | Sim | RF-ING-002 |
| TTL do artefato cru | Tempo até apagar o artefato cru após publish | Número (horas) | Curto (minimização LGPD) | Inteiro positivo | Admin | Painel de configuração da agência | Próximo ciclo de limpeza | Sim | RF-ING-004 |
| Retenção de snapshots, comentários e analytics | Tempo de guarda dos dados do cliente | Número (dias) | A declarar pelo admin | Inteiro positivo | Admin | Painel de configuração da agência | Próximo ciclo de limpeza/offboarding | Sim | RF-ADM-011 |
| Allowlist de fontes de benchmark e pesquisa de mercado | Quais fontes externas a IA pode citar | Lista de domínios/fontes | Vazia | Lista editável | Admin (por agência) e Super Admin (global) | Painel de configuração | Imediato, próxima composição | Sim | RF-CMP-014, RF-ING-006 |
| Identidade padrão da agência | Fallback quando a ficha do cliente está vazia | JSON (cores, tom de voz) | Nenhum até o admin declarar | Livre, validado como cor/tom | Admin | Painel de identidade da agência | Imediato, próximo publish sem ficha do cliente | Sim | RF-ADM-006 |
| Templates (tipos de relatório) | Blocos padrão por tipo de relatório | CRUD de entidade | Nenhum até alguém criar; a biblioteca de uma agência começa com os Globais que existirem | Livre | Admin e Operador nos individuais da própria agência; só o Super Admin nos Globais | Biblioteca de templates | Imediato, próximo relatório com esse template (relatório já criado não muda, RN-013 item 6) | Sim | RF-ADM-009, RN-013 |
| Escopo do template (individual ou Global) | Se o template vale só na agência de origem ou em todas | Booleano por template (is_global) | Individual (falso) no nascimento | Individual ou Global | Só o Super Admin | Biblioteca de templates, visão do Super Admin | Imediato, a biblioteca de todas as agências passa a listar ou deixa de listar | Sim | RF-ADM-016, RN-013 |
| Teto de verbosidade por bloco | Limite de caracteres por bloco de texto | Número (caracteres) | Definido no schema (ex.: 280 para insight) | Inteiro positivo | Super Admin (global) | Configuração do schema de blocos | Imediato, próxima composição | Sim | RF-CMP-015 |
| Rate-limit do link público | Requisições por janela ao caminho do link público | Número (requisições por janela) | A definir na Fatia 9, admin-editável | Inteiro positivo | Super Admin (global) e Admin (por agência) | Painel de configuração | Imediato, próximo acesso | Sim | RF-PUB-002 |
| Mapa objetivo para métrica primária (âncora do selo) | Qual métrica é a âncora do selo de honestidade para cada objetivo_campanha do briefing (Ap.1) | Tabela (objetivo do enum para metric_id) | Seed: vendas para receita ou ROAS; leads para leads ou CPL; trafego para cliques ou CPC; reconhecimento para alcance ou CPM; engajamento para engajamento | Só objetivos do enum e métricas do catálogo | Admin (por agência) e Super Admin (default global) | Painel de configuração | Imediato, próxima composição | Sim | RN-012 ii, RF-CMP-018 |
| Tolerância de largura de janela | Diferença máxima entre as larguras das duas janelas comparadas antes de a comparação ser tratada como parcial | Número (percentual) | 10 por cento | 0 a 100 | Admin (por agência) | Painel de configuração | Imediato, próxima composição | Sim | RF-CMP-021 |
| Desfecho da comparação parcial | O que fazer quando as janelas comparadas têm larguras diferentes além da tolerância | Enum fechado (recusar a variação, ou emitir com marca de janela parcial) | Emitir com marca de janela parcial | Só os dois valores | Admin (por agência) | Painel de configuração | Imediato, próxima composição | Sim | RF-CMP-021 |
| Métricas não somáveis entre janelas | Quais métricas usam o pico entre janelas em vez da soma quando o relatório agrega mais de uma janela | Lista de metric_id | Seed: alcance, usuários únicos, visitantes únicos | Só métricas do catálogo | Admin (por agência) | Painel de configuração | Imediato, próxima composição | Sim | RF-CMP-022 |
| Base sazonal da cadência mensal | Se um relatório mensal compara também com o mesmo mês do ano anterior, além do mês imediatamente anterior | Booleano | Desligado | Ligado ou desligado | Admin (por agência) | Painel de configuração | Imediato, próxima composição | Sim | RN-014 item 4 |
| Limiar de materialidade da variação do dado | A partir de que variação nos pontos de dado da âncora e das métricas destacadas o dado é considerado "mudado" na classificação de quadrantes do anti-fadiga | Número (percentual) | 5 por cento | 0 a 100 | Admin (por agência) | Painel de configuração | Imediato, próxima composição | Sim | RF-CMP-016 |
| Histórico mínimo por tipo de bloco | Quantos períodos uma seção exige antes de renderizar, abaixo do que ela se oculta e vira lacuna declarada | Tabela (tipo de bloco para inteiro) | Seed: line_chart 3; comparison com base de período anterior 2; rankings 2 | Inteiro maior ou igual a 1 | Admin (por agência) | Painel de configuração | Imediato, próxima composição | Sim | RF-REN-014 |
| Teto de tempo por bloco | Tempo máximo de composição de um bloco isolado antes de ele ser interrompido sem derrubar a composição | Número (segundos) | 6 | Inteiro positivo | Super Admin (global) e Admin (por agência) | Painel de configuração | Imediato, próxima composição | Sim | RNF-PERF-002 |
| Piso e teto do tempo de documento | Piso do teto de documento e limite superior do teto escalado (número de blocos multiplicado pelo teto por bloco) | Tabela (2 números, segundos) | Piso 60; limite superior a definir por agência | Inteiro positivo, limite superior maior que o piso | Super Admin (global) e Admin (por agência) | Painel de configuração | Imediato, próxima composição | Sim | RNF-PERF-002 |
| Guardas com override permitido | Quais guardas não estruturais aceitam a escotilha com justificativa escrita. As estruturais (número sem proveniência, publicar sem sessão humana) NÃO entram nesta lista e a lista não as aceita | Lista de identificadores de guarda | Seed: todas as guardas não estruturais de RF-CUR-009 | Só guardas não estruturais do catálogo | Super Admin (global) | Painel de configuração | Imediato, próxima composição | Sim | RF-CUR-009 |
| Limiar de alerta de excesso de ressalvas | Razão entre ressalvas e achados de uma página a partir da qual o sistema alerta o admin de que o relatório está ficando inútil por excesso de guarda | Número (razão) | 1,0 (mais ressalvas do que achados) | Maior que 0 | Admin (por agência) | Painel de configuração | Imediato, próximo publish | Sim | RF-CUR-009 |
Para que serve: ser a lista nominal e fechada das vinte métricas da camada de resultado, com a classe de origem, a fórmula das derivadas, o estado dessa fórmula e a visibilidade de cada uma. É o artefato que impede duas coisas de uma vez: uma métrica de resultado exibida sem origem declarada, e um número comercial da agência (o fee, a margem, os lucros) atravessando para a página que o cliente abre.
Relacionado com: RF-CMP-031, RF-REN-017, RF-ING-008, Ap.10.2, {Q8}; tarefas {F20-01}, {F20-06} e {F20-07} do Roadmap; artefatos {ART-ENT-15} (os parâmetros que alimentam as derivadas) e {ART-CFG-01} (os demais parâmetros de agência).
Onde fica: catálogo em código, com a visibilidade de cada métrica exposta como parâmetro por agência no painel do admin.
Especificação: a ficha completa das vinte entradas está no Ap.10.2 do DRS (REGRA-INV-2), com uma linha por métrica trazendo número, nome, classe de origem, forma, estado da forma e visibilidade default; as regras de leitura do dono do negócio estão no Ap.10.3. Estado medido em 2026-08-03: 2 de classe artefato, 3 de classe parâmetro e 15 derivada, das quais 4 com fórmula conferida (transcrita de nota do autor na planilha) e 11 a conferir. O que este cartão fixa como configuração:
| Parâmetro | Descrição | Tipo | Valor default (seed) | Validação | Quem edita | Onde edita | Efeito | Auditado | Regra |
|---|---|---|---|---|---|---|---|---|---|
| Visibilidade por métrica de resultado | Se a métrica pode aparecer na página que o cliente abre ou é só de gestão interna | Tabela (métrica para enumerado de 2: interna, cliente) | O default de cada uma das 20 está no Ap.10.2; nasce interna para fee, margem de contribuição, multiplicador de LTV e os lucros derivados deles | Só as 20 métricas do Ap.10.2; só os 2 valores | Admin (por agência) | Painel de configuração | Imediato, próximo publish; nenhum snapshot anterior muda | Sim | RF-REN-017, {Q8} |
| Estado da fórmula por métrica derivada | Se a fórmula daquela métrica já foi conferida contra a planilha de gestão que é a fonte | Tabela (métrica para enumerado de 2: a conferir, conferida) | Todas as derivadas nascem a conferir | Só as métricas de classe derivada do Ap.10.2 | Super Admin (global) | Painel de configuração | Imediato, próxima composição | Sim | RF-CMP-031, {F20-01} |
A regra que sustenta a segunda linha e que vale desde já: uma métrica derivada com estado a conferir NÃO é emitida em relatório. Ela existe no catálogo para que a conferência tenha alvo nominal, não para ser usada antes de conferida. Uma fórmula errada aqui não quebra nada, produz um número verdadeiro na forma e falso no valor, que é a classe de erro que ninguém reporta. E fórmula conferida não basta: a métrica só é emitida quando todos os operandos dela também resolvem, o que hoje bloqueia o CAC Payback, cuja fórmula é conferida mas cujo operando Margem média por cliente não está definido em lugar nenhum. O corte de visibilidade acontece no PUBLISH, removendo a métrica do conteúdo do snapshot: esconder no render não basta, porque o snapshot é servido a quem tem o link.