Repertórios — Design Técnico
Gerado pelo Writer (Reversa) em 2026-05-11
doc_level: detalhado
Interface
Redux Store — Thunks (src/store/Repertorios/Repertorios.action.ts)
| Símbolo |
Assinatura |
Retorno |
Observação |
carregarRepertorios |
() |
ThunkAction |
Busca todos não excluídos; armazena no EntityAdapter |
criarRepertorio |
(titulo: string) |
ThunkAction |
INSERT com Timestamp.now; despacha Metricas.repertorioCriado |
atualizarRepertorio |
(id, titulo: string) |
ThunkAction |
UPDATE título; valida ≤ 50 chars |
desativarRepertorio |
(id_repertorio: number) |
ThunkAction |
Soft-delete: SET data_exclusao = agora |
adicionarMusicaAoRepertorio |
(id_repertorio, id_musica) |
ThunkAction |
INSERT item + atualiza ordem_musicas; despacha métrica |
adicionarTermoAoRepertorio |
(id_repertorio, termo: string) |
ThunkAction |
INSERT item sem id_musica; valida ≤ 300 chars |
removerItemDoRepertorio |
(id_item_repertorio, id_repertorio) |
ThunkAction |
DELETE item + atualiza ordem_musicas |
atualizarItemRepertorio |
(id_item_repertorio, { momento?, tonalidade? }) |
ThunkAction |
UPDATE com trim; despacha sugestões de autocomplete |
duplicarRepertorio |
(id_repertorio) |
ThunkAction |
Copia cabeçalho + itens; slug fica undefined; despacha métrica |
compartilharRepertorio |
(id_repertorio) |
ThunkAction |
Chama CompartilharService.repertorio; veja fluxo abaixo |
gerarFolheto |
(id_repertorio) |
ThunkAction |
Chama FolhetoService.gerarFolheto; seleciona estratégia |
importarRepertorioPorSlug |
(slug: string) |
ThunkAction |
Chama RepertorioService.findRepertorioSimilar + cria local |
Repository (src/repository/repertorios/RepertoriosRepository.ts)
| Símbolo |
Assinatura |
Retorno |
Confiança |
findAllBy |
() |
Promise<Repertorio[]> |
🟢 |
findById |
(id_repertorio: number) |
Promise<Repertorio> |
🟢 |
createRepertorio |
(titulo: string) |
Promise<number> |
🟢 |
updateRepertorio |
(id, titulo: string) |
Promise<void> |
🟢 |
deactivateRepertorio |
(id: number) |
Promise<void> |
🟢 |
createItemRepertorio |
(id_repertorio, item: Partial<ItemRepertorio>) |
Promise<number> |
🟢 |
deleteItemRepertorio |
(id_item_repertorio: number) |
Promise<void> |
🟢 |
updateItemRepertorio |
(id_item_repertorio, item) |
Promise<void> |
🟢 |
updateOrdemMusicas |
(id_repertorio, ordem: number[]) |
Promise<void> |
🟢 |
Esquema de Dados (SQL)
repertorio (
id_repertorio INTEGER PRIMARY KEY,
titulo TEXT NOT NULL CHECK(length(titulo) <= 50),
resumo TEXT,
ordem_musicas TEXT DEFAULT '[]', -- JSON array de id_musica
slug_referencia TEXT,
slug_corrente TEXT,
metadata TEXT DEFAULT '{}', -- JSON: { data?, dia_liturgico? }
tipo_repertorio INTEGER DEFAULT 0, -- 0=normal, 1=padrão (não excluível)
data_exclusao INTEGER -- soft-delete: timestamp UNIX ou NULL
)
item_repertorio (
id_item_repertorio INTEGER PRIMARY KEY,
id_repertorio INTEGER NOT NULL REFERENCES repertorio(id_repertorio),
id_musica INTEGER, -- NULL quando item é texto livre
termo TEXT CHECK(term IS NULL OR length(termo) <= 300),
momento TEXT,
tonalidade TEXT,
data_criacao INTEGER
)
🟢 CHECK constraints confirmados via migration v8.
Fluxo Principal: Compartilhamento de Repertório
compartilharRepertorio(id_repertorio)
→ CompartilharService.getToken()
→ Auth.autenticar() [se necessário]
→ Google OAuth PKCE → POST /auth → recebe Bearer JWT
→ Api.compartilharRepertorio(bearer, repertorio, itens)
→ POST /api/v1/repertorios (novo) ou PUT /api/v1/repertorios/{slug} (atualização)
→ Recebe { conta: { slug }, slug } do servidor
→ RepertoriosRepository.updateSlugCorrente(id, slug.corrente)
→ RepertorioService.gerarMensagemCompartilhamento(repertorio, itens, url)
→ titulo + metadata (data pt-BR longa + nome do dia litúrgico romcal)
→ para cada item: tonalidade (código inline) + momento (italic) + título/termo
→ se tem livro: sigla + índice
→ URL: https://canta.app/repertorios/?{conta}:{slug}
→ Share.share(mensagemMarkdown) [dialog nativo do SO]
→ Metricas.compartilharRepertorio()
Fluxo: Geração de Folheto
gerarFolheto(id_repertorio)
→ FolhetoService.gerarFolheto(repertorio)
→ Seleciona estratégia:
· GerarFolhetoComSlugStrategy → autenticar → compartilharRepertorio → openLink(livreto.canta.app/?conta:slug)
· GerarFolhetoBase64Strategy → convertToQuery → Base64Unicode.encode → openLink(livreto.canta.app/?repertorio=BASE64)
· GerarFolhetoNoopStrategy → sem ação (fallback)
→ Metricas.gerarFolheto()
Fluxo: Importação por Slug
importarRepertorioPorSlug(slug)
→ RepertorioService.findRepertorioSimilar(slug)
→ Se slug já existe localmente → retorna id do repertório existente
→ Se não → Api.getRepertorioPorSlug(slug)
→ Mapeia RepertorioPersistidoResponse → Repertorio (preserva slug.referencia)
→ createRepertorio(titulo) + para cada item: createItemRepertorio(...)
→ retorna id_repertorio novo
→ Navega para a tela do repertório importado
Fluxo: Duplicar Repertório
duplicarRepertorio(id_repertorio)
→ RepertoriosRepository.findById(id_repertorio)
→ createRepertorio(titulo + " (cópia)")
→ para cada item em repertorio.itens → createItemRepertorio (slug = undefined)
→ Metricas.repertorioDuplicado()
Dependências
| Módulo / Serviço |
Papel |
Confiança |
Auth (arch/auth) |
Provê Bearer JWT para compartilhamento e folheto com slug |
🟢 |
Api Canta.app |
POST/PUT repertório; GET repertório por slug |
🟢 |
Share API (RN) |
Dialog nativo de compartilhamento de texto |
🟢 |
CalendarioLiturgicoService |
Resolve nome do dia litúrgico para metadado de mensagem |
🟢 |
Metricas (Sentry) |
Eventos: repertorio_criado, repertorio_duplicado, gerar_folheto |
🟢 |
livreto.canta.app |
Site externo para visualização e download do folheto PDF |
🟢 |
Decisões de Design Relevantes (ver decisions.md)
| Decisão |
Resumo |
ordem_musicas como JSON array |
Evita N queries de reordenação; array inteiro é atualizado atomicamente |
| Soft-delete em vez de hard-delete |
Preserva histórico para possível sincronização futura; repertórios padrão ficam inacessíveis via soft-delete |
Dual slug (referencia + corrente) |
Rastreia origem do repertório importado mesmo após re-compartilhamento pelo novo dono |
| Múltiplas estratégias de folheto |
Permite degradação graciosa: se o slug falhar, Base64 ainda funciona |
| Reordenação desabilitada (DT-08) |
Feature planejada mas bloqueada por instabilidade da lib de reordenação |