Skip to content

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