O Mapas Culturais é uma plataforma open-source de mapeamento e gestão cultural, usada por municípios, estados e órgãos federais no Brasil. Ela permite que gestores públicos criem editais e oportunidades culturais (cursos, prêmios, fomentos), recebam inscrições de artistas e produtores, avaliem essas inscrições por meio de comissões, e acompanhem os projetos aprovados.
Para quem já conhece a base de código, o ciclo principal é: um agente cria uma Oportunidade com múltiplas Fases (inscrição, seleção, recurso, prestação de contas). Inscrições (registrations) fluem pelas fases conforme são selecionadas. Cada fase pode ter uma Configuração de Método de Avaliação (técnica, qualificação documental, contínua), com critérios e seções. Avaliadores são distribuídos por comissões, e suas notas geram um resultado consolidado.
Até a versão 7.7, o ciclo de vida de uma oportunidade terminava essencialmente na publicação do resultado final. Havia uma fase de prestação de informações (reporting), mas sem estrutura para acompanhar modificações no projeto aprovado. O plano de metas era básico — apenas título, descrição e entregas com poucos campos. O sistema de avaliação funcionava, mas sofria com problemas de integridade quando critérios eram editados após o início das avaliações, e a distribuição de avaliadores não respeitava quotas por comissão.
Se você precisasse resumir a v7.8 em uma frase: ela transforma o Mapas Culturais de um sistema de seleção em um sistema de gestão de projetos culturais. As três grandes apostas são:
Um projeto cultural é aprovado. O proponente recebe o fomento e começa a executar. Mas projetos culturais são vivos: a data do evento muda, o orçamento precisa de ajustes, o local não está mais disponível. Até a v7.7, não havia mecanismo estruturado para registrar e avaliar essas alterações. O gestor não tinha visibilidade, e o proponente não tinha canal formal.
A fase de execução é uma nova fase no ciclo de vida da oportunidade, inserida após a publicação do resultado final e antes da fase de prestação de informações. O proponente aprovado pode abrir pedidos de alteração, cada um avaliado por uma comissão de avaliação independente. Os pedidos ficam registrados no histórico da inscrição e não bloqueiam as fases seguintes.
O gestor cria a fase via modal simples, definindo datas de inscrição e avaliação (que devem ser posteriores à data de publicação do resultado final). A fase usa método de avaliação simples e o formulário é montado livremente, sem categorias fixas.
O proponente aprovado abre um pedido de alteração. O sistema cria uma nova inscrição na fase de execução, vinculada à inscrição original pelo campo number e por previousPhaseRegistrationId. Avaliadores da comissão avaliam o pedido dentro da janela de datas.
A fase de execução é excluída do sincronismo automático de inscrições entre fases. Isso é intencional: os pedidos de alteração são independentes e não devem bloquear ou afetar as fases seguintes de prestação de informações. Os métodos enqueueRegistrationSync, syncRegistrations e importPreviousPhaseRegistrations retornam false para fases de execução.
Os pedidos aparecem em três lugares: na ficha da inscrição original, na linha do tempo de fases, e na lista do gestor — todos identificados com o número da inscrição original e a ordem de criação. Apenas uma fase de execução é permitida por oportunidade (validado por hasExecutionPhase).
O plano de metas era, essencialmente, um conjunto de metas com entregas contendo poucos campos (título, descrição, período, valor). Na v7.8, ele se torna um sistema completo de planejamento e acompanhamento, com dezenas de campos configuráveis cobrindo todas as dimensões de um projeto cultural.
Cada campo de entrega (delivery) agora existe em dois tempos: planejado (preenchido na inscrição) e executado (preenchido no monitoramento). Por exemplo, totalBudget registra o orçamento previsto, enquanto executedTotalBudget registra quanto foi efetivamente gasto. Essa dualidade se repete para todos os novos campos.
| Categoria | Exemplos de campos | Tipo |
|---|---|---|
| Orçamento e produção | totalBudget, unitPrice, commercialUnits |
currency, number |
| Equipe | paidStaffByRole, teamCompositionGender, teamCompositionRace |
JSON array/object |
| Alcance geográfico | numberOfCities, numberOfNeighborhoods |
number |
| Comunicação | hasPressStrategy, communicationChannels |
boolean, multiselect |
| Acessibilidade | hasAccessibilityPlan, expectedAccessibilityMeasures |
boolean, multiselect (~30 opções) |
| Comunidades e inclusão | hasCommunityCoauthors, hasTransInclusionStrategy |
boolean + text detail |
| Meio ambiente | hasEnvironmentalPractices |
boolean + text |
| Inovação e documentação | hasInnovationAction, documentationTypes |
boolean, multiselect |
Cada campo é controlado por um par de flags na oportunidade: workplan_deliveryInform<Field> (exibir) e workplan_deliveryRequire<Field> (obrigatório). O mesmo padrão se repete para monitoramento: workplan_monitoringInform<Field> e workplan_monitoringRequire<Field>. Isso permite ao gestor configurar exatamente quais dimensões são relevantes para cada edital, sem forçar todos os campos em todas as oportunidades.
A responsabilidade de validação do plano de metas foi transferida do módulo ProjectMonitoring para OpportunityWorkplan. Agora, toda a lógica de validação — tanto para a fase de inscrição quanto para a de monitoramento — vive em um único lugar. Os erros são reportados de forma estruturada via workplanProxy, aninhados como errors.workplanProxy.deliveries[id][field][].
Dois novos hooks (SpreadsheetJob.getHeader:after e SpreadsheetJob.getBatch:after) adicionam colunas do plano de metas à exportação de inscrições. Aproximadamente 25 colunas por inscrição, incluindo duração do projeto, segmento cultural, detalhes de metas e todos os campos de entrega. Múltiplas metas/entregas são separadas por pipe (|), booleanos renderizados como "Sim"/"Não", e campos JSON são achatados.
As mudanças no sistema de avaliação são as mais densas desta release. Elas atacam quatro problemas que afetavam editais em produção: a rigidez do bônus de pontuação, a fragilidade na edição de critérios, a distribuição desigual de avaliadores, e a propagação incorreta entre fases.
O sistema de bônus por políticas afirmativas (cotas) suportava apenas bônus percentuais. Agora suporta dois modos:
Ambos suportam um teto configurável (roof) que limita o bônus máximo aplicado. Se as configurações de bônus forem alteradas após resultados publicados, o sistema bloqueia a modificação para não-administradores e recalcula automaticamente todas as avaliações já enviadas.
Até a v7.7, era possível criar critérios órfãos (referenciando seções deletadas) ou com campos obrigatórios vazios — especialmente porque a interface de configuração faz auto-save a cada alteração, salvando estados intermediários enquanto o gestor ainda está digitando.
Na v7.8, três mecanismos trabalham juntos:
sanitizeCriteriaSectionsDraft()): roda antes de cada save(), removendo seções sem nome, critérios sem campos obrigatórios, e critérios órfãos. Isso lida com os estados intermediários do auto-save.validateCriteriaSectionsIntegrity()): roda como regra de validação da entidade. Verifica se todos os critérios referenciam seções válidas e têm campos obrigatórios preenchidos. Para qualificação documental: cada seção deve ter pelo menos um critério. Para avaliação técnica: seções vazias são removidas automaticamente.validateCriteriaDeletion()): impede que não-administradores removam critérios ou seções quando existem avaliações iniciadas. Administradores podem excluir, e removeDeletedCriteriaFromEvaluations() limpa as notas/respostas correspondentes das avaliações existentes e reconsolida o resultado.O algoritmo de distribuição de inscrições para avaliadores foi reescrito com lógica de quotas por comissão. O fluxo é:
maxRegistrations configurado recebem até esse limite.shuffle).
A query SQL foi corrigida para eliminar linhas duplicadas (havia um LEFT JOIN redundante em registration_evaluation). Avaliações já realizadas agora são preservadas durante a redistribuição, evitando que um avaliador que já completou sua avaliação seja desatribuído.
Avaliadores das comissões agora recebem números sequenciais, visíveis na interface, na tabela e na planilha de avaliações. Atualização automática é aplicada em editais já existentes via db update. Busca de avaliadores agora suporta ID do agente no formato #ID e e-mail do usuário.
O método apiFindRegistrations() foi reescrito com uma otimização significativa para oportunidades multi-fase. Quando a query usa @limit, a fase alvo é consultada primeiro com paginação, e as fases anteriores são consultadas apenas pelo campo number (sem carregar a entidade completa). Isso evita o carregamento N+1 de inscrições de fases anteriores em oportunidades grandes.
As quotas de inscrição coletiva agora são aplicadas apenas à fase alvo, não às fases anteriores — que são consultadas como lookups suplementares.
Novo endpoint POST_deleteEvaluationAndRemoveValuer() permite remover um avaliador de uma inscrição específica e deletar sua avaliação submetida em uma única operação. Requer permissão modifyValuers. O fluxo: adiciona o avaliador à valuersExcludeList, remove da valuersIncludeList, atualiza a coluna JSONB valuers via SQL raw, e deleta a entidade RegistrationEvaluation (com lookup multi-estratégia).
A blocklist de MIME types foi expandida para incluir php3–php8, phtml, phar, x-python, x-perl, x-sh, svg+xml, xml, yaml, sql, ini, log, entre outros. Uma nova whitelist regex-based (app.default_allowed_mime_types) valida tipos permitidos quando o grupo de arquivos não define a sua própria. A blocklist de extensões (app.not_allowed_extensions) bloqueia .php, .exe, .sh, .py, .svg, .xml, .env, .sql, etc.
O loop N+1 em findOpportunitiesModels foi substituído por uma query SQL única. Flushes ao banco ao gerar oportunidades a partir de modelos foram reduzidos. A listagem de modelos agora identifica corretamente os oficiais pelo selo verificado.
Novo job ImportFields para importação de campos de formulário em background, com templates de e-mail de sucesso (import_fields_success) e erro (import_fields_error). A importação via TXT agora preserva corretamente os filtros por categoria, tipo de proponente e faixa.
PROXY_HEADER): identifica o IP do visitante em ambientes com proxy (Cloudflare, etc.).$registeredFileGroups).
O arquivo mais alterado em volume é src/core/App.php — 4.910 linhas de diff. Mas a maior parte é cosmética: uma passagem de formatação automatizada que converteu aspas simples para duplas, braces de K&R para Allman, adicionou trailing commas em chamadas multi-linha, e traduziu docblocks de inglês para português. Não houve mudança estrutural — nenhum método foi adicionado ou removido.
Três bug fixes reais estavam escondidos na formatação:
new $class($id);; → new $class($id);case 'k'; → case "k": — um bug latente que causava fallthrough silencioso no getMaxUploadSize().@property-read → @property: sinaliza que certas propriedades podem agora ser escritas via magic setter.| Arquivo | Linhas | Propósito |
|---|---|---|
OpportunityExecution/Module.php |
647 | Fase de execução — pedidos de alteração e avaliação |
EvaluationConsolidationTest.php |
776 | Testes do novo bônus e reconsolidação |
PhaseRegistrationSyncTest.php |
989 | Testes de propagação entre fases e recurso |
EvaluationMethodTechnicalTest.php |
3.237 | Cobertura extensiva da avaliação técnica |
OpportunityPhasesGettersTest.php |
1.163 | Testes dos getters de fase (main, upstream, downstream) |
PointRewardBonusTest.php |
880 | Cobertura dos modos fixo e percentual com teto |
| Arquivo | Δ linhas | Área de mudança |
|---|---|---|
OpportunityWorkplan/Module.php |
+1.958 | Novos campos, validação unificada, exportação |
ProjectMonitoring/Module.php |
+612 | Campos executados, persistência, serialização |
OpportunityPhases/Module.php |
+604 | Appeal phase, sync, getters, date validation |
EvaluationMethod.php |
+601 | Quotas, redistribution, consolidation |
EvaluationMethodTechnical/Module.php |
+532 | Point reward, criteria sanitization |
ApiQuery.php |
+587 | PHPDoc, file group filtering |
Controllers/Opportunity.php |
+598 | Paginated phases, import job, docblocks |
// Converte configurações legadas (array com fieldPercent)
// para o formato canônico com type e rules[].bonusValue
public function normalizePointRewardConfig(array $config): array
{
if (isset($config['type'])) {
return $config; // já está no formato novo
}
// Legado: array de regras com fieldPercent
$rules = [];
foreach ($config as $rule) {
$rules[] = [
'field' => $rule['field'],
'bonusValue' => $rule['fieldPercent'],
];
}
return [
'type' => 'percentage',
'rules' => $rules,
];
}
// Roda antes de cada save() para limpar estados
// intermediários do auto-save da UI
protected function sanitizeCriteriaSectionsDraft(
array &$sections,
array &$criteria
): void {
// Remove critérios órfãos (sectionId inexistente)
$sectionIds = array_column($sections, 'id');
$criteria = array_filter($criteria,
fn($c) => in_array(
$c['sectionId'] ?? null, $sectionIds
)
);
// Remove seções sem nome (auto-save intermediário)
$sections = array_filter($sections,
fn($s) => !empty(trim($s['name'] ?? ''))
);
}
A v7.8.0 é uma release de expansão funcional significativa. O Mapas Culturais passa a cobrir o ciclo completo de um projeto cultural — da inscrição ao acompanhamento pós-aprovação — e ganha profundidade em três dimensões críticas: planejamento de projetos (workplan), robustez de avaliações, e segurança de uploads.
As atualizações automáticas de banco (_dbUpdates()) cuidam de normalizar bônus de pontuação antigos e preencher números sequenciais de avaliadores em editais existentes. A flag de envio de e-mails de recurso está agora desligada por padrão — se seu edital usa e-mails de recurso, habilite-a explicitamente. O PROXY_HEADER deve ser configurado se você usa Cloudflare ou proxy similar.