Skip to content

Músicas — Design Técnico

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

Interface

Redux Store

Símbolo Assinatura Retorno Observação
carregarMusica (id_musica: number) ThunkAction Verifica store antes de query; upsert no EntityAdapter
buscarMusicas (termo: string, livro?: number, page?: number) ThunkAction Despacha para MusicasRepository.findAllBy
setFavorita (id_musica: number, favorita: boolean) ThunkAction Apenas persiste no SQLite; store não muda imediatamente
marcarMusicaComoVisualizada (id_musica: number) ThunkAction Side effect assíncrono; adiciona ao repertório id=2

Repository

Símbolo Assinatura Retorno Confiança
MusicasRepository.findAllBy (params: BuscaParams) Promise<PaginacaoResponse<Musica>> 🟢
MusicasRepository.findById (id_musica: number) Promise<InformacoesDetalhadasMusica> 🟢
getDetalhesTermoBusca (termo: string) { tipo: 'indice' \| 'texto', valor } 🟢

Fluxo Principal: Busca de Músicas

  1. Usuário digita termo na barra de busca
  2. buscarMusicas é despachado com { termo, livro?, page? }
  3. MusicasRepository.findAllBy chama getDetalhesTermoBusca(termo):
  4. Se parseInt(termo) é válido → estratégia porIndice: WHERE cantico_livro.indice = ?
  5. Se termo é vazio → estratégia semTermo: SELECT ... ORDER BY titulo
  6. Se termo é texto → estratégia porTermo:
    • NFKD normalize + uppercase → remove não-alfanumérico → split por espaço → cada token recebe * → join com espaço → remove **
    • musica_busca MATCH ? ORDER BY bm25(musica_busca, 50, 5) (título peso 50, letra peso 5)
  7. Query usa LIMIT n+1: se retornou n+1, hasNextPage = true; o item extra não é retornado ao cliente
  8. JOIN com cantico_livro e edicao_livro para enriquecer com índices e informações do livro
  9. Resultado é armazenado no Redux store (lista paginada)
  10. Componente renderiza lista com snippet de destaque nos resultados FTS5

Fluxos Alternativos

  • Livro selecionado: WHERE cantico_livro.id_edicao_livro = ? é acrescentado em todas as estratégias
  • Rótulo selecionado: filtro adicional em musica.rotulos (JSON array) via json_each
  • Sem resultados: componente renderiza estado vazio
  • Erro de banco: 🔴 comportamento de erro não mapeado com clareza — provavelmente propaga exceção ao Redux

Fluxo: Carregamento de Letra

  1. Usuário seleciona uma música da lista
  2. carregarMusica(id_musica) é despachado
  3. Verifica se id_musica já está no EntityAdapter do store
  4. Se sim: retorna do cache, sem query
  5. Se não: MusicasRepository.findById(id_musica)
    • SELECT musica JOIN cantico_livro JOIN edicao_livro JOIN livro JOIN editora
    • Mapeia para InformacoesDetalhadasMusica com indices: IndiceMusica[]
  6. Redux: upsertOne no EntityAdapter
  7. Keep Awake é ativado (activateKeepAwake)
  8. Side effect: marcarMusicaComoVisualizada(id_musica) — assíncrono, não bloqueia
  9. Componente renderiza letra completa

Fluxo: Favoritar / Desfavoritar

  1. Usuário toca no ícone de favorito
  2. setFavorita(id_musica, favorita) despachado
  3. MusicasFavoritasRepository.update(id_musica, favorita) persiste no SQLite
  4. Em caso de sucesso: Metricas.favoritarMusica() ou desfavoritarMusica() é chamado
  5. O store Redux NÃO é atualizado imediatamente — estado do favorito vem de query separada ao banco
  6. Em caso de erro: Toast de erro exibido ao usuário

Dependências

  • MusicasFavoritasRepository — persistência de favoritos e histórico de visualizações em SQLite
  • Metricas — tracking de eventos de favoritar/visualizar (Sentry)
  • CalendarioLiturgicoService — não diretamente, mas rótulos litúrgicos são referenciados em CategoriaRotulos
  • Redux EntityAdapter — cache in-memory de músicas carregadas por id
  • Keep Awake (react-native-keep-awake) — ativado na tela de letra

Decisões de Design

Decisão Evidência no código Confiança
FTS5 com tabela virtual musica_busca para busca performática Migration que cria musica_busca + MusicasRepository 🟢
LIMIT n+1 para detectar hasNextPage sem COUNT(*) findAllBy — cláusula LIMIT 🟢
EntityAdapter (Redux Toolkit) para evitar queries duplicadas por id Musicas.reducer.ts — uso de createEntityAdapter 🟢
Campo YouTube deprecated em favor de YouTubes[] (múltiplos vídeos) Musica.ts + commit feat(youtube) 🟢
Configuração adaptativa phone/tablet para snippet e trecho da letra MusicaConfiguration.ts 🟢
Favoritar não atualiza store imediatamente (leitura e escrita desacopladas) setFavorita — ausência de dispatch de atualização 🟢

Estado Interno

Fatia Redux Campos relevantes Persistência
Musicas entities (EntityAdapter), ids, status, error Apenas sessão
BuscaMusicas termo, livroSelecionado, pagina, resultados 🟡 parcialmente via MMKV (preferências)

Observabilidade

  • Metricas.favoritarMusica() — evento de favoritar (Sentry) 🟢
  • Metricas.desfavoritarMusica() — evento de desfavoritar (Sentry) 🟢
  • Metricas.visualizarMusica() — evento de visualização de letra (Sentry) 🟢
  • Erro de banco: não há log explícito mapeado além do toast de erro 🔴

Riscos e Lacunas

  • 🔴 Comportamento exato ao tentar carregar uma música com id_musica inválido ou inexistente no banco não foi mapeado — requer validação
  • 🟡 O filtro por rótulo usa json_each no SQLite — performance em catálogos grandes pode ser um problema
  • 🟡 A sanitização especial da letra de Salmo (RN-06) não foi localizada no código com precisão — pode estar em transformação na view
  • 🔴 Keep Awake não é desativado explicitamente ao sair da tela em todos os cenários — verificar unmount/cleanup