Skip to content

Músicas — Decisões de Design

Gerado pelo Writer (Reversa) em 2026-05-11 doc_level: detalhado

DECISION-01 — FTS5 com tabela virtual musica_busca para busca textual

Status: Implementado (desde o início do projeto)

Contexto: O catálogo pode conter milhares de músicas. A busca precisa ser rápida, offline e tolerante a acentos.

Decisão: Tabela virtual FTS5 musica_busca espelhando musica.titulo (peso BM25 = 50) e musica.letra (peso BM25 = 5). Tokenizer unicode61 para suporte a acentos nativo + normalização NFKD aplicada no código antes do MATCH.

Alternativas descartadas:

  • LIKE com índice — não suporta ranking, lento em textos longos
  • Busca em memória — não escalável
  • API de busca remota — viola requisito de uso offline

Consequências:

  • 🟢 Busca sub-100ms em catálogos de 5k+ músicas
  • 🟢 Ranking automático por relevância via BM25
  • 🟡 Tabela FTS5 precisa ser mantida sincronizada com musica (via triggers ou re-insert)

Rastreabilidade: docs/reversa/adrs/004-sqlite-fts5-banco-principal.md | src/repository/musicas/MusicasRepository.ts


DECISION-02 — Estratégia tripla de busca (semTermo / porIndice / porTermo)

Status: Implementado

Contexto: A busca precisa lidar com três intenções distintas do usuário: navegar o catálogo, buscar pelo número do hinário físico, e buscar por texto livre.

Decisão: Padrão Strategy com 3 implementações selecionadas por getDetalhesTermoBusca:

  • Entrada vazia → semTermo (ORDER BY titulo)
  • parseInt(termo) válido → porIndice (WHERE cantico_livro.indice = ?)
  • Qualquer outro texto → porTermo (FTS5 MATCH)

Consequências:

  • 🟢 UX fluida: usuário com hinário físico em mãos digita o número e acha a música
  • 🟡 Índices alfanuméricos (ex.: "23a") não são reconhecidos como índice — fallback para FTS5 pode não encontrar

Rastreabilidade: src/repository/musicas/MusicasRepository.tsBuscaStrategies


DECISION-03 — LIMIT n+1 para detecção de hasNextPage sem COUNT(*)

Status: Implementado

Contexto: Paginação infinite scroll precisa saber se há mais resultados, sem executar um COUNT(*) separado.

Decisão: Consultar LIMIT pageSize + 1. Se retornar pageSize + 1 registros, hasNextPage = true e o último item é descartado. Caso contrário, hasNextPage = false.

Consequências:

  • 🟢 Evita dupla query (data + count)
  • 🟢 Performático em listas grandes
  • 🟢 Padrão amplamente conhecido, sem surpresas

Rastreabilidade: src/repository/musicas/MusicasRepository.tsfindAllBy


DECISION-04 — Redux EntityAdapter para cache de músicas por id

Status: Implementado

Contexto: A tela de letra e a tela de lista são navegadas frequentemente de forma alternada. Recarregar do banco a cada navegação é desnecessário.

Decisão: createEntityAdapter do Redux Toolkit mantém um mapa {id_musica → InformacoesDetalhadasMusica} em memória. carregarMusica verifica esse mapa antes de ir ao banco.

Consequências:

  • 🟢 Zero latência ao retornar para uma música já carregada
  • 🟡 Cache não é invalidado automaticamente — músicas atualizadas via obras da Biblioteca podem ficar desatualizadas até reiniciar o app

Rastreabilidade: src/store/Musicas/Musicas.reducer.ts


DECISION-05 — Campo YouTube (singular) mantido como deprecated

Status: Deprecated, mantido por compatibilidade

Contexto: O backend evoluiu para suportar múltiplos vídeos YouTube por música. O campo singular existia antes dessa mudança.

Decisão: Manter midias.YouTube?: string no modelo mas usar midias.YouTubes?: string[] como fonte de verdade. O campo singular não deve ser removido até que todas as músicas antigas sejam migradas.

Consequências:

  • 🟢 Compatibilidade retroativa com músicas do catálogo mais antigo
  • 🟡 Pontos de consumo na UI devem tratar o fallback explicitamente

Rastreabilidade: src/model/Musica.ts | commit feat(youtube): support to api with multiple youtube ids


DECISION-06 — Configuração adaptativa phone/tablet via MusicaConfiguration

Status: Implementado

Contexto: Tablets têm telas maiores e podem exibir trechos maiores da letra na listagem sem comprometer o layout.

Decisão: Arquivo de configuração retorna constantes diferentes com base em isTablet():

  • Phone: TOTAL_CARACTERES=100, TOTAL_TOKENS=12
  • Tablet: TOTAL_CARACTERES=250, TOTAL_TOKENS=24

Consequências:

  • 🟢 UX aprimorada no tablet sem código condicional espalhado
  • 🟡 A detecção isTablet() pode ser imprecisa em alguns dispositivos Android com tela grande

Rastreabilidade: src/repository/musicas/MusicaConfiguration.ts