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
- Usuário digita termo na barra de busca
buscarMusicas é despachado com { termo, livro?, page? }
MusicasRepository.findAllBy chama getDetalhesTermoBusca(termo):
- Se
parseInt(termo) é válido → estratégia porIndice: WHERE cantico_livro.indice = ?
- Se termo é vazio → estratégia
semTermo: SELECT ... ORDER BY titulo
- 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)
- Query usa
LIMIT n+1: se retornou n+1, hasNextPage = true; o item extra não é retornado ao cliente
- JOIN com
cantico_livro e edicao_livro para enriquecer com índices e informações do livro
- Resultado é armazenado no Redux store (lista paginada)
- 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
- Usuário seleciona uma música da lista
carregarMusica(id_musica) é despachado
- Verifica se
id_musica já está no EntityAdapter do store
- Se sim: retorna do cache, sem query
- 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[]
- Redux:
upsertOne no EntityAdapter
- Keep Awake é ativado (
activateKeepAwake)
- Side effect:
marcarMusicaComoVisualizada(id_musica) — assíncrono, não bloqueia
- Componente renderiza letra completa
Fluxo: Favoritar / Desfavoritar
- Usuário toca no ícone de favorito
setFavorita(id_musica, favorita) despachado
MusicasFavoritasRepository.update(id_musica, favorita) persiste no SQLite
- Em caso de sucesso:
Metricas.favoritarMusica() ou desfavoritarMusica() é chamado
- O store Redux NÃO é atualizado imediatamente — estado do favorito vem de query separada ao banco
- 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