-- ============================================================================= -- Migration: 20260417000001_dev_tables -- Área de Desenvolvimento (dev_*) — roadmap, auditoria, concorrentes, logs -- ----------------------------------------------------------------------------- -- Tabelas usadas pela página /saas/desenvolvimento. Todas restritas a -- saas_admins via RLS (helper public.is_saas_admin()). -- ============================================================================= -- ----------------------------------------------------------------------------- -- Helper trigger: updated_at -- ----------------------------------------------------------------------------- CREATE OR REPLACE FUNCTION public.dev_set_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at := now(); RETURN NEW; END; $$; -- ============================================================================= -- 1. dev_roadmap_phases — Fases (1, 2, 3...) -- ============================================================================= CREATE TABLE IF NOT EXISTS public.dev_roadmap_phases ( id BIGSERIAL PRIMARY KEY, numero INTEGER NOT NULL UNIQUE, nome VARCHAR(160) NOT NULL, objetivo TEXT, timeline_sugerida VARCHAR(160), criterio_saida TEXT, status VARCHAR(20) NOT NULL DEFAULT 'planejada' CHECK (status IN ('planejada','em_andamento','concluida','arquivada')), data_inicio DATE, data_fim DATE, ordem INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dev_roadmap_phases_status ON public.dev_roadmap_phases(status); CREATE INDEX IF NOT EXISTS idx_dev_roadmap_phases_ordem ON public.dev_roadmap_phases(ordem); DROP TRIGGER IF EXISTS trg_dev_roadmap_phases_updated_at ON public.dev_roadmap_phases; CREATE TRIGGER trg_dev_roadmap_phases_updated_at BEFORE UPDATE ON public.dev_roadmap_phases FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at(); -- ============================================================================= -- 2. dev_roadmap_items — Itens das fases -- ============================================================================= CREATE TABLE IF NOT EXISTS public.dev_roadmap_items ( id BIGSERIAL PRIMARY KEY, phase_id BIGINT NOT NULL REFERENCES public.dev_roadmap_phases(id) ON DELETE CASCADE, numero INTEGER, bloco VARCHAR(160), feature TEXT NOT NULL, descricao TEXT, esforco VARCHAR(4) CHECK (esforco IS NULL OR esforco IN ('S','M','L','XL')), prioridade VARCHAR(20) CHECK (prioridade IS NULL OR prioridade IN ('bloqueador','alta','media','diferencial')), status VARCHAR(20) NOT NULL DEFAULT 'pendente' CHECK (status IN ('pendente','em_andamento','concluido','cancelado','bloqueado')), notas TEXT, assignee VARCHAR(120), data_inicio DATE, data_conclusao DATE, ordem INTEGER NOT NULL DEFAULT 0, tags TEXT[] DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dev_roadmap_items_phase ON public.dev_roadmap_items(phase_id); CREATE INDEX IF NOT EXISTS idx_dev_roadmap_items_status ON public.dev_roadmap_items(status); CREATE INDEX IF NOT EXISTS idx_dev_roadmap_items_prior ON public.dev_roadmap_items(prioridade); CREATE INDEX IF NOT EXISTS idx_dev_roadmap_items_ordem ON public.dev_roadmap_items(phase_id, ordem); DROP TRIGGER IF EXISTS trg_dev_roadmap_items_updated_at ON public.dev_roadmap_items; CREATE TRIGGER trg_dev_roadmap_items_updated_at BEFORE UPDATE ON public.dev_roadmap_items FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at(); -- ============================================================================= -- 3. dev_auditoria_items — Bugs / débitos técnicos / decisões -- ============================================================================= CREATE TABLE IF NOT EXISTS public.dev_auditoria_items ( id BIGSERIAL PRIMARY KEY, categoria VARCHAR(120), titulo TEXT NOT NULL, descricao_problema TEXT, solucao TEXT, severidade VARCHAR(20) CHECK (severidade IS NULL OR severidade IN ('critico','alto','medio','baixo')), status VARCHAR(20) NOT NULL DEFAULT 'aberto' CHECK (status IN ('aberto','em_analise','resolvido','wontfix','duplicado')), resolvido_em DATE, sessao_resolucao VARCHAR(160), arquivo_afetado TEXT, tags TEXT[] DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dev_auditoria_items_status ON public.dev_auditoria_items(status); CREATE INDEX IF NOT EXISTS idx_dev_auditoria_items_severidade ON public.dev_auditoria_items(severidade); CREATE INDEX IF NOT EXISTS idx_dev_auditoria_items_categoria ON public.dev_auditoria_items(categoria); DROP TRIGGER IF EXISTS trg_dev_auditoria_items_updated_at ON public.dev_auditoria_items; CREATE TRIGGER trg_dev_auditoria_items_updated_at BEFORE UPDATE ON public.dev_auditoria_items FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at(); -- ============================================================================= -- 4. dev_competitors — Concorrentes -- ============================================================================= CREATE TABLE IF NOT EXISTS public.dev_competitors ( id BIGSERIAL PRIMARY KEY, slug VARCHAR(80) NOT NULL UNIQUE, nome VARCHAR(160) NOT NULL, pais VARCHAR(40), foco VARCHAR(160), pricing TEXT, posicionamento TEXT, url TEXT, ultima_pesquisa DATE, notas TEXT, ativo BOOLEAN NOT NULL DEFAULT true, ordem INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dev_competitors_ativo ON public.dev_competitors(ativo); CREATE INDEX IF NOT EXISTS idx_dev_competitors_pais ON public.dev_competitors(pais); DROP TRIGGER IF EXISTS trg_dev_competitors_updated_at ON public.dev_competitors; CREATE TRIGGER trg_dev_competitors_updated_at BEFORE UPDATE ON public.dev_competitors FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at(); -- ============================================================================= -- 5. dev_competitor_features — features de cada concorrente -- ============================================================================= CREATE TABLE IF NOT EXISTS public.dev_competitor_features ( id BIGSERIAL PRIMARY KEY, competitor_id BIGINT NOT NULL REFERENCES public.dev_competitors(id) ON DELETE CASCADE, categoria VARCHAR(120), nome TEXT NOT NULL, descricao TEXT, fonte VARCHAR(20) NOT NULL DEFAULT 'publico' CHECK (fonte IN ('fetched','observacao','publico','hipotese')), fonte_url TEXT, data_fonte DATE, destaque BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dev_competitor_features_comp ON public.dev_competitor_features(competitor_id); CREATE INDEX IF NOT EXISTS idx_dev_competitor_features_cat ON public.dev_competitor_features(categoria); CREATE INDEX IF NOT EXISTS idx_dev_competitor_features_destaque ON public.dev_competitor_features(destaque); DROP TRIGGER IF EXISTS trg_dev_competitor_features_updated_at ON public.dev_competitor_features; CREATE TRIGGER trg_dev_competitor_features_updated_at BEFORE UPDATE ON public.dev_competitor_features FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at(); -- ============================================================================= -- 6. dev_comparison_matrix — AgenciaPsi × features-de-concorrente -- ============================================================================= CREATE TABLE IF NOT EXISTS public.dev_comparison_matrix ( id BIGSERIAL PRIMARY KEY, dominio VARCHAR(120), feature TEXT NOT NULL, nosso_status VARCHAR(20) NOT NULL DEFAULT 'a_definir' CHECK (nosso_status IN ('tem','parcial','gap','na','a_definir')), nossa_nota TEXT, importancia VARCHAR(20) CHECK (importancia IS NULL OR importancia IN ('alta','media','baixa')), ordem INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dev_comparison_matrix_dominio ON public.dev_comparison_matrix(dominio); CREATE INDEX IF NOT EXISTS idx_dev_comparison_matrix_status ON public.dev_comparison_matrix(nosso_status); DROP TRIGGER IF EXISTS trg_dev_comparison_matrix_updated_at ON public.dev_comparison_matrix; CREATE TRIGGER trg_dev_comparison_matrix_updated_at BEFORE UPDATE ON public.dev_comparison_matrix FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at(); -- dev_comparison_competitor_status — opcional: status por concorrente por feature -- (se quisermos marcar que competitor X tem feature Y). Tabela ponte N-N. CREATE TABLE IF NOT EXISTS public.dev_comparison_competitor_status ( id BIGSERIAL PRIMARY KEY, comparison_id BIGINT NOT NULL REFERENCES public.dev_comparison_matrix(id) ON DELETE CASCADE, competitor_id BIGINT NOT NULL REFERENCES public.dev_competitors(id) ON DELETE CASCADE, status VARCHAR(20) NOT NULL DEFAULT 'a_definir' CHECK (status IN ('tem','parcial','gap','na','a_definir')), nota TEXT, fonte VARCHAR(20) CHECK (fonte IS NULL OR fonte IN ('fetched','observacao','publico','hipotese')), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (comparison_id, competitor_id) ); CREATE INDEX IF NOT EXISTS idx_dev_ccs_comp ON public.dev_comparison_competitor_status(competitor_id); CREATE INDEX IF NOT EXISTS idx_dev_ccs_comparison ON public.dev_comparison_competitor_status(comparison_id); DROP TRIGGER IF EXISTS trg_dev_ccs_updated_at ON public.dev_comparison_competitor_status; CREATE TRIGGER trg_dev_ccs_updated_at BEFORE UPDATE ON public.dev_comparison_competitor_status FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at(); -- ============================================================================= -- 7. dev_generation_log — histórico de execuções (backup, dashboard, export...) -- ============================================================================= CREATE TABLE IF NOT EXISTS public.dev_generation_log ( id BIGSERIAL PRIMARY KEY, tipo VARCHAR(40) NOT NULL, comando TEXT, sucesso BOOLEAN NOT NULL DEFAULT false, stdout TEXT, stderr TEXT, duration_ms INTEGER, metadata JSONB DEFAULT '{}'::jsonb, trigger_user_id UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dev_generation_log_tipo ON public.dev_generation_log(tipo); CREATE INDEX IF NOT EXISTS idx_dev_generation_log_created ON public.dev_generation_log(created_at DESC); -- ============================================================================= -- RLS — tudo restrito a saas_admins (helper existente: public.is_saas_admin()) -- ============================================================================= DO $$ DECLARE t TEXT; dev_tables TEXT[] := ARRAY[ 'dev_roadmap_phases', 'dev_roadmap_items', 'dev_auditoria_items', 'dev_competitors', 'dev_competitor_features', 'dev_comparison_matrix', 'dev_comparison_competitor_status', 'dev_generation_log' ]; BEGIN FOREACH t IN ARRAY dev_tables LOOP EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY;', t); -- Drop policy se existir (idempotente) EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I;', t || '_saas_admin_all', t); -- Cria policy que permite tudo pra saas_admin EXECUTE format( 'CREATE POLICY %I ON public.%I FOR ALL TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin());', t || '_saas_admin_all', t ); END LOOP; END $$; -- ============================================================================= -- Comentários -- ============================================================================= COMMENT ON TABLE public.dev_roadmap_phases IS 'Fases do roadmap (MVP, Paridade, Diferenciação). Visível só pra saas_admins.'; COMMENT ON TABLE public.dev_roadmap_items IS 'Itens de cada fase do roadmap.'; COMMENT ON TABLE public.dev_auditoria_items IS 'Bugs, dívidas técnicas e decisões arquiteturais.'; COMMENT ON TABLE public.dev_competitors IS 'Concorrentes analisados no benchmark.'; COMMENT ON TABLE public.dev_competitor_features IS 'Features catalogadas de cada concorrente.'; COMMENT ON TABLE public.dev_comparison_matrix IS 'Matriz de comparação AgenciaPsi × features esperadas do mercado.'; COMMENT ON TABLE public.dev_comparison_competitor_status IS 'Qual concorrente tem qual feature (ponte N-N com matrix).'; COMMENT ON TABLE public.dev_generation_log IS 'Histórico de execuções (backup, dashboard, export, seed, etc).';