Phantom Unit OS · mapa do sistema e ordem de construção
Todo nó, toda seta, todo disparo, todo prazo, todo botão e todo arquivo — com o estado real de cada um. O mapa é o plano: onde ele está vermelho é onde falta construir.
Uma linha só, de cima pra baixo. Cada nó diz quem age, o que cria no banco, o que dispara e quanto tempo tem. As ramificações saem à direita.
convites — código, artist_id, papel, validade 30d
e-mail pelo Resend, contato@phantomunit.com.br · retry pelo cron
duas portas: pessoa nova · re-vínculo (troca a porta de quem já existe)
o WhatsApp digitado no modal do artista é descartado — o INSERT não tem a coluna
pessoas + pessoa_projeto + pessoa_funcao
sessão de 30 dias em sessoes, cookie pu_sessao
o vínculo é pelo LINK, não pelo e-mail — logar com outra conta Google funciona igual
convite vencido + pessoa que já existe = entra calado e o projeto não vincula
fichas (declaração, trava) + artists (vitrine, sempre editável)
"deixar pra depois" faz o mesmo POST — a ficha vira enviada e nunca mais abre
CPF é opcional ali, e não existe segunda porta pra digitar depois
artist_name · legal_name · cpf · email · whatsapp
2 o lançamento — release_title · release_type · genre · data_lancamento
3 as faixas — title · version · bpm · key · collab · master_option + WAV e mixdown
4 a capa — cover_option (própria ou do designer) + conceito e referência
5 o contrato — cpf_aceite · declaration_security
submit.html:1373 inventa id:'PHANTOM-4823' → ficha.js:88 procura essa ficha, não acha → 404 "nao_encontrada"
o array faixas[] é coletado e jogado fora — achata() só lê track.title
releases · release_artists · tracks
cria as 4 linhas de release_etapas:
capa
wav
mkt
distro
faixa_artistas fica vazio — não existe um único INSERT nessa tabela em todo o app
data_alvo não é gravado no nascimento — só quando mkt/distro destravam. Hoje: 0 de 1.428 linhas
deveria disparar pro designer e pro master que entrou trabalho — não dispara
covers/ · jpg/png · teto 25 MB · registra arquivos.capa3000
o upload move o estado por SQL direto — sem etapa_eventos, sem avisar o Bagre
aprovar → 🟢 · recusar → volta pra 🔴 com o motivo
masters/ · só .wav · teto 300 MB · registra arquivos.master
o prefixo masters/ só sai com sessão, e responde 404 sem login — não 401
mesmo defeito: sobe e ninguém é avisado
data_alvo é gravado — o relógio conta da liberação, não da criação
e nunca passa da data de lançamento: de nada adianta ter 3 dias se o disco sai amanhã
avisa a Amanda e o Basso que foram chamados pro jogo
mkt/ · teto 500 MB · ~120 MB (1 reel + 4-5 artes + 1 vídeo)
mkt/ não está na lista de prefixos trancados — nasceu público no R2
o CHECK de arquivos.tipo recusa "mkt": sobe no R2 e estoura no INSERT
APROVAR é impossível de clicar: mede o estado em vez do arquivo. A coluna dela está morta na tela
masters/
aprovar → distribuído → trava definitiva dos arquivos
mp3/ · capa web 1200 → capaweb · reel de preview → previews/
Tem que existir ANTES do original esfriar. Se inverter, o WAV vai pro armário e o painel fica sem o que tocar
hoje: 324 capaweb e 115 mp3 no banco, todos vindos da migração do catálogo — o app não gera nenhum
A esteira é rotativa: entra quando o lançamento nasce, sai 30 dias depois de sair nas lojas. Ela é função do ritmo (~25 lançamentos/ano), não do tamanho do acervo — com 300 ou 3.000 releases ela continua com os mesmos ~2 GB. Vitrine + Esteira cabem na franquia grátis de 10 GB. O que paga é só o Armário.
Quatro estados, quatro ações, e um ciclo. O estado de origem nunca vem da tela — é lido do banco na hora. Se o navegador pudesse dizer "estava em andamento", bastava mentir pra aprovar o que nem foi entregue.
"o que acontece quando o masterizador recusa a master? de laranja volta pra vermelho, isso
gera um alerta pro artista" — é exatamente isso, e a regra já está escrita no código.
recusar leva 🟡 andamento → 🔴 pendente, a nota é obrigatória (recusa sem motivo
é só um "não", e o artista fica sem saber o que arrumar), e o aviso sai uma linha por pessoa do
projeto — duo recebe nos dois contatos.
Só falta o botão. As quatro chamadas de moverEtapa no painel são todas
'aprovar'. RECUSAR e REABRIR não existem em lugar nenhum da tela.
| Ação | Vai de | Pra | Quem pode | Dispara | tela |
|---|---|---|---|---|---|
| entregar | 🔴 pendente | 🟡 andamento | dono do projeto (capa/wav), quem responde pela etapa, ou admin | avisa quem aprova | ◐ |
| aprovar | 🟡 andamento | 🟢 concluído | quem tem o papel da etapa, ou admin | avisa o artista | ● |
| recusar | 🟡 andamento | 🔴 pendente | quem tem o papel da etapa, ou admin | avisa o artista, com o motivo | ○ |
| reabrir | 🟢 concluído | 🔴 pendente | só admin | — | ○ |
◐ existe pela metade (o upload move o estado por fora e não avisa) · ● tem botão · ○ sem botão
1. Toda bolinha tem dono e próxima ação. 2. Toda bolinha é filtro — clicar nela abre a fila daquele estado. 3. Bolinha sem estado real por trás é pior que texto: parece informação e não é.
Onde isso está sendo quebrado hoje: a aba ARTISTAS pinta 207 artistas de vermelho por "sem lançar há mais de um ano" — vermelho sem dono e sem próxima ação, que é julgamento e não trabalho. E o painel cobra 92 tarefas de releases que já saíram. Quando 80% da tela é vermelha, o vermelho para de puxar o olho em todas as outras.
Nada aqui é opcional: metade do painel de trabalho é sobre coisa que não aconteceu, e ninguém clica em "não fiz nada ainda". Por isso existe o relógio.
| # | Quando | Vai pra quem | Canal | estado |
|---|---|---|---|---|
| 1 | convite criado | a pessoa convidada | ||
| 2 | convite de re-vínculo | a pessoa que já existe | e-mail (texto próprio) | |
| 3 | etapa liberada | quem responde por ela | ||
| 4 | alguém entregou | quem aprova aquela etapa | ||
| 5 | etapa aprovada | o artista — uma linha por pessoa do projeto | ||
| 6 | etapa recusada | o artista, com o motivo | ||
| 7 | capa e wav fecharam → destravou | Amanda e Basso | ||
| 8 | metade do prazo | quem responde pela etapa | e-mail · cron 9h | |
| 9 | faltam 24h | quem responde pela etapa | e-mail · cron 9h | |
| 10 | prazo estourou | quem responde pela etapa | e-mail · cron 9h | |
| 11 | membro parado há mais de um ano | a direção | e-mail · cron 9h | |
| 12 | saque solicitado | contato@ + WhatsApp Nathan e Amanda | e-mail + zap | |
| 13 | saque pago | o artista |
O cron das 9h roda, filtra WHERE data_alvo IS NOT NULL, e não acha nada:
data_alvo é nulo nas 1.428 linhas de release_etapas. Os
prazo_dias da tabela funcoes (7 · 7 · 3 · 3) são decoração.
E foi exatamente essa funcionalidade que motivou sair do Pages pro Workers. O motor está montado, ligado e girando em falso — falta só alguém gravar a data-alvo quando o lançamento nasce.
| Etapa | Papel | Quem é hoje | prazo | Conta a partir de |
|---|---|---|---|---|
| 🎨 CAPA | designer | Bagre | 7 dias | criação do lançamento |
| 🎚️ WAV | master | Lutz | 7 dias | criação do lançamento |
| 📣 MKT | marketing | Amanda | 3 dias | da liberação (capa + wav 🟢) |
| 🚚 DISTRO | manager | Basso | 3 dias | da liberação |
E o teto é sempre a data de lançamento: se o prazo cair depois dela, vale a data. O relógio roda uma vez por dia, às 9h de Brasília — prazo de 7 dias não muda de status entre 9h e 10h. A fila de avisos é esvaziada de 15 em 15 minutos, como rede de segurança de quem falhou no envio imediato.
Subir arquivo não avisa ninguém. O upload move a etapa por SQL direto, sem passar pela rota que dispara o aviso. O Bagre e o Lutz só descobrem se abrirem o painel.
A fila não tem trava de posse. Cron e rota podem pegar a mesma linha e mandar duas vezes. Não mordeu porque só existem 7 avisos no banco.
O WhatsApp ainda não está ligado — a Evolution precisa de um VPS, e a URL está vazia no
wrangler.toml. Hoje tudo é e-mail. E e-mail é registro; WhatsApp é o aviso que a
galera vê.
O eixo não é tamanho. É por quanto tempo o app precisa servir aquele arquivo. O banco não se divide: o D1 continua sendo o catálogo inteiro, e sabe onde cada byte está. Só os bytes se espalham por função.
| Camada | O que mora | Onde | Acesso | tamanho | estado |
|---|---|---|---|---|---|
| 🪟 Vitrine | capa web 1200 · MP3 do player · reel de preview | covers/ mp3/ previews/ | capa pública · mp3 e preview só com login | ~7 GB | |
| 🔧 Esteira | master · capa 3000 · pacote de mkt em produção até +30d | masters/ covers/ mkt/ | só com login · 404 sem sessão | ~2 GB rotativo | |
| 📦 Armário | os mesmos, já lançados | R2 Infrequent Access | só com login | ~65 GB | |
| 🚫 Fora | stems · projeto · mockup · vídeo bruto · pacotes de mkt antigos | Dropbox / backup | — | — |
masters/ · só .wav · 300 MB · arquivos.master
o Basso baixa pra distribuir · o artista vê que está lá
no lançamento: trava (ninguém altera mais) · +30 dias: esfria pro armário
a extensão é conferida no servidor e a chave é montada lá — o cliente nunca escolhe o caminho
mp3/, interno de propósito: é o acervo, não a vitrine pública
toca no perfil da música dentro do app, pra o artista ver que a música dele está ali
nenhum código do app gera MP3. Os 115 que existem vieram da migração do catálogo
covers/ (esteira, depois armário) · web 1200 → vitrine, pública
324 de cada no banco · a API pública da Apple resolveu 239 sem uma falha
mkt/ · ~120 MB: 1 reel (~15) + 4-5 artes (~15) + 1 vídeo YouTube (~90)
do pacote, só o reel fica quente (vira preview na página) — o resto vai direto pro armário
nasceu público no R2 e o registro no banco estoura. Zero uploads até hoje
Na página pública do release: embed de Spotify/YouTube — o play conta como stream e vira royalty. Player próprio ali seria pagar storage pra perder dinheiro em cada play. No painel interno: MP3 real, porque ali ninguém ia streamar de qualquer jeito.
Hoje as duas páginas públicas tocam um MP3 de demonstração genérico ao lado do texto "Qualidade Master 24-bit", e é ele que faz a página levar 18 segundos pra terminar de carregar.
41 botões no painel, mais os que os geradores criam em tempo de execução. Um por um, com o que cada um faz de verdade.
| • | Botão | O que faz |
|---|---|---|
| DASHBOARD · ARTISTAS · MÚSICAS · FINANCEIRO · CHAT · CONFIG | troca de aba sem recarregar · a aba do endereço manda no boot · cada uma busca os dados na 1ª abertura | |
| ↩ SAIR | apaga a linha em sessoes, não só o cookie | |
| 🔔 AVISAR NO CELULAR | inscreve no Web Push (PWA) |
| • | Botão | O que faz |
|---|---|---|
| + CADASTRAR ARTISTA | cria o projeto no casting e convida · trata "já existe" em verde, sem duplicar | |
| + EQUIPE | convite sem artist_id, com seletor de papel — é a porta da Amanda | |
| ✉️ CONVIDAR | convite com artist_id — sem isso a discografia dele fica órfã | |
| ⚡ VER PERFIL · 👁️ GERENCIAR PERFIL | abre a vitrine do projeto | |
| SALVAR ALTERAÇÕES | grava em artists · o nome oficial não entra: só admin renomeia | |
| bolinhas de perfil | logo · foto · descrição · links — clicáveis como filtro e ordenáveis |
| • | Botão | O que faz |
|---|---|---|
| CAPA · MASTER · MKT · DISTRIBUIÇÃO (cards do topo) | filtram a fila daquela etapa · ainda cinza, fora do padrão de etiqueta verde | |
| bolinhas da tabela | cada uma é filtro · title explica o estado do ponto de vista de quem lê | |
| ⚡ GERENCIAR | abre a ficha do lançamento com os 4 módulos | |
| ⬇ ORIGINAL · ⬇ BAIXAR WAV · ⬇ BAIXAR PACK | puxam do R2 · BAIXAR PACK nunca liga (o mesmo bug do APROVAR do mkt) | |
| APROVAR (capa · wav · distro) | 🟡 → 🟢 · avisa o artista · capa+wav fechando destrava mkt e distro | |
| APROVAR (mkt) | impossível de clicar — mede o estado em vez do arquivo | |
| RECUSAR | não existe — a rota existe, o texto do aviso existe, o botão não | |
| REABRIR | não existe — a rota é admin-only e está pronta | |
| ⧉ COPIAR METADADOS | leva a ficha pro painel da distribuidora | |
| zona de arrastar | upload de capa · WAV · pack | |
| 💬 WHATSAPP do responsável | abre a conversa · o número de teste ainda está em um deles |
| • | Botão | O que faz |
|---|---|---|
| SOLICITAR SAQUE | só aparece com a janela aberta e ninguém tendo pedido · modal com PIX digitado na hora | |
| CONFIRMAR PEDIDO | ehDono antes de gravar · valor recalculado no servidor · idempotente por projeto+trimestre | |
| AGUARDANDO PAGAMENTO | botão desabilitado amarelo — é estado, não ação: deveria ser um selo, não ocupar o lugar do botão | |
| exportar CSV do trimestre | a planilha de pagamento · hoje qualquer logado baixa | |
| MARCAR COMO PAGO | não existe — o 🟡 → 🟢 só acontece por SQL na mão |
| • | Botão | O que faz de verdade |
|---|---|---|
| SALVAR CHAVES DE API | alert('✅ Chaves salvas com sucesso!') — e os campos vêm com chave falsa preenchida | |
| 🟢 SPOTIFY · 📸 INSTAGRAM · + APURAÇÃO | desabilitados com "EM BREVE" — estes estão certos, dizem a verdade | |
| 4× "Baixar…" no press kit público | alert('Baixando fotos oficiais HD...') — nada baixa, em 227 páginas | |
| PRÉ-SAVE (público) | alert('PRÉ-SAVE REALIZADO COM SUCESSO!') — não salva nada, pro público | |
| ▶ PLAY PREVIEW (público) | toca uma faixa genérica de demonstração |
Dois arquivos servem 584 páginas. Mexer no layout é mexer em um arquivo, e todas mudam junto — artista novo já nasce com página. É a maior alavanca do sistema, e hoje é o que está pior.
| • | Página | Serve | Lê de | Situação |
|---|---|---|---|---|
| /artistas/<slug> | 228 press kits | /api/roster?id= | 14 de 21 do casting quebram por title:null · UTTARI e zap falso em todas | |
| /musicas/<slug> | 350 lançamentos | /api/roster?release= | pré-save de mentira · o bloco ROYALTY já vem no HTML, escondido só por CSS | |
| /casting.html | a grade do roster | /api/roster | carregando, erro e vazio renderizam dentro de uma bolinha de 11px | |
| /index.html | a vitrine da label | nada | zero chamada de API · 11 fotos de um gerador aleatório · "Uttari" listado como artista | |
| /login.html | a entrada | /api/convite-info | a única página sem nenhum achado |
Na página de música, o bloco #section-live já chega ao visitante anônimo
populado com o royalty — £467,21, £6,24, o detalhe por loja — e o que segura é um bug: o
script nunca chama setMode('live'). Consertar o modo sem antes tirar a coluna
ROYALTY publica o faturamento por faixa. Os dois têm que andar juntos, e nessa ordem.
Corre por fora da esteira, no ritmo do trimestre. É a parte mais bem construída do sistema.
Label Name = 'Phantom Unit' (o arquivo traz 5 gravadoras) → sales
o EAN vira float (...476.0) e não casa com nada — e não dá erro, só nunca encontra
release novo chega com número PH e já existe como PU: funde por id, nunca cria outro
hoje é script manual · importador dentro do app não existe
faixa_artistas
2 resolver cada dono no perfil → artist_aliases
3 aí somar por perfil — nunca somar por texto e juntar depois
colab divide entre os projetos primeiro; a taxa de cada um se aplica depois
bruto 13.252,58 = artistas 7.580,54 + label 5.672,04 · órfão 0,00
royalties, por projeto+trimestre
e-mail pro contato@ + WhatsApp pro Nathan e pra Amanda · com protocolo
PIX digitado na hora, nunca guardado no perfil · copia-e-cola gerado no Worker, sem valor
o extrato é em £ e o PIX em R$ — e nada na tela explica isso ao artista
não existe botão de marcar como pago, e não há índice único: já entraram 3 linhas no mesmo trimestre
Sete fases. Cada uma existe porque destrava a seguinte — não é lista de desejo, é dependência. A fase 0 é a única que não pode esperar.
Por quê: tem dado de artista na internet aberta agora. Nada do que vem depois vale a pena construir em cima de uma porta aberta.
phantom-release-hub — 176 URLs de deploy sem Access, ligadas no D1 de produçãoroster.js inteiro: lista explícita de colunas, tirar o gbp de faixas e lojas, tirar o WhatsApp das duas consultasfinanceiro.js, antes de ramificar por parâmetrorevincular: true — ou parar de devolver o link do convite na respostaPor quê: hoje nenhum lançamento consegue nascer no app. Enquanto isso for verdade, todo o resto da esteira é teoria.
id inventado no primeiro POSTfaixa_artistas com o faixas[] que o formulário já coleta e joga foradata_alvo no nascimento de capa e wav — é a linha que liga o relógio na tomadaetapa_eventos e disparar o avisobloqueado — hoje dá pra pular capa e master subindo o ZIPPor quê: sem RECUSAR não existe ciclo de correção — existe carimbo. E a coluna da Amanda está morta na tela.
tem_mkt na rotamkt/ no R2 e corrigir o CHECK de arquivos.tipoUNIQUE(artist_id, period) antesPor quê: o GreenTech entrou e recebeu a interface de staff — o roster inteiro, os 350 lançamentos, e a aba do próprio dinheiro travada.
/api/dashboard já faz no servidorPH/GM do app.html:2318 — ele esconde 64 lançamentos na visão do artistareturn mudo: hoje três catch deixam a tela em "carregando…" pra semprePor quê: 227 press kits e 350 páginas de lançamento entregam hoje o nome de um artista que nunca existiu, downloads que não baixam e um pré-save que não salva.
title: null no banco — uma correção de dado que conserta 14 press kits de uma vezalert('Baixando…'), o pré-save falso e o áudio de demonstraçãorelease-page.html — antes de consertar o setModebio do artista aparecer no press kit — ele escreve e nunca é lidobooking@phantomunit.com → .com.brPor quê: o preview tem que existir antes do original esfriar. Se inverter, o WAV vai pro armário e o painel fica sem o que tocar.
Por quê: hoje existem dois front-ends divergindo, e nenhuma tela responde "o que eu preciso fazer agora" — só "o que a label tem parado, por função".
frio vira cinza "não se aplica" — vermelho é trabalho, e 207 artistas de vermelho matam o vermelho--brand-accent separado dos 4 --estado-*/novo → /app com a matriz de paridade na mãoO sistema não está pela metade — ele está construído e desligado. A máquina de estado tem as quatro transições certas, os 13 avisos têm texto pronto, o relógio roda de 15 em 15 minutos, o rateio fecha ao centavo e a arquitetura de armazenamento está desenhada até o custo em reais.
O que falta são ligações, não peças: uma linha que grave a data-alvo, uma que grave o dono da faixa, um botão de recusar, um escopo que caia pela sessão. São poucas linhas em lugares certos — e cada uma delas acende um pedaço inteiro que já está pronto esperando.
A fase 0 é a exceção. Ela não constrói nada: ela fecha uma porta que está aberta agora.