Dashboard
Visão geral
Banco
Online
ERP Flow
Bom dia 👋
Visão executiva · tempo real
💰
Faturamento do Mês
R$ 0,00
— total do período
📈
Lucro do Mês
R$ 0,00
— margem média
🔧
Pedidos Ativos
0
em produção agora
💳
Saldo Financeiro
R$ 0,00
entradas − saídas
📋
0
Orçamentos
0
Aprovados
🔄
0%
Conversão
🎯
R$ 0
Ticket Médio
R$ 0
A Receber
💰
R$ 0
Fat. Total
📊
Vendas por Dia — últimos 14 dias
🏆
Produtos Mais Vendidos
Nenhuma venda ainda
🔻
Funil de Orçamentos
Nenhum orçamento
🏭
Produção Ativa
Nenhum pedido
🕐
Últimos Orçamentos
Nenhum ainda
Ações Rápidas
📋 Novo Orçamento
Fluxo guiado em 6 etapas
📦
Produto
Defina o produto, NCM e código fiscal
Banner, Fachada ACM, Adesivo, Letra Caixa...
Código interno para controle de estoque
Nomenclatura Comum do Mercosul — obrigatório na NF-e
📐 Dimensões & Quantidade
Área Total 0.0000 m²
Etapa 1 de 6
🎯 Simulador de Bobina
Calcule o aproveitamento real da mídia e o custo com desperdício
🎞️ Dados da Bobina (Mídia)
Ex: 1.52m, 1.60m, 2.10m
Metros lineares do rolo
📐 Área total da bobina:  ·  💰 Custo por m²:
📦 Produto a Imprimir
Espaço entre cada peça
Opções Avançadas
📊 Resultado do Aproveitamento
Peças que cabem
Bobinas necessárias
Desperdício
Aproveitamento
Formação de Custo
Preencha os dados e clique em Calcular
Visualização do Corte
🗂️ Orçamento por Projeto
Monte propostas com múltiplos itens, serviços e arte
📋 Dados da Proposta
📦 Itens da Proposta
Descrição do Item
Largura×Altura
Qtd
Tipo
Valor Unit.
Ações
📦 Nenhum item adicionado. Clique em "+ Adicionar Item" para começar.
Custo Total
R$ 0,00
Lucro Estimado
R$ 0,00
Margem Média
0%
VALOR TOTAL
R$ 0,00
📂 Propostas Salvas
Nenhuma proposta salva ainda.
Orçamentos
0 orçamentos
ℹ️ Nenhum orçamento ainda.
Clientes
Cadastro de clientes
NomeTelefoneE-mailCidadeAções
Nenhum cliente
🎨 Materiais
Tabela de custos e preços · local
Total de Materiais
0
Ativos
0
Custo Médio/m²
R$ 0,00
Markup Médio
0%
MaterialTipoCusto/m²Preço/m²MarkupLargura RoloFonteStatusAções
Carregando...
✏️
Cadastrar Material
0%
💡 Supabase: Configure em Configurações → 🗄️ Supabase para sincronizar materiais no banco de dados.
Produção
Controle de pedidos e produção
Aguardando
0
Em Produção
0
Prontos p/ Entregar
0
Entregues (mês)
0
Valor em Produção
R$ 0,00
🔧 Produção 0
🖨 Impressão 0
✂️ Acabamento 0
📦 Pronto 0
🚚 Entregue 0
💰 Financeiro
Fluxo de caixa · entradas · saídas · saldo
💵
Entradas (pagas)
R$ 0,00
Mês atual: R$ 0,00
💸
Saídas (pagas)
R$ 0,00
Mês atual: R$ 0,00
📊
Saldo Atual
R$ 0,00
Entradas − Saídas
A Receber
R$ 0,00
0 pendentes
📅 Janeiro 2025
Faturamento
R$ 0,00
Despesas
R$ 0,00
Resultado
R$ 0,00
Lançamentos
0
DataTipoDescriçãoOrigemValorCategoriaVencimentoStatusAções
Nenhum lançamento
🧾 Fiscal
Produtos, Serviços e Notas Fiscais
Cadastre produtos físicos com NCM para emissão de NF-e
NCM = Nomenclatura Comum do Mercosul — obrigatório na NF-e
💡 NCMs comuns para comunicação visual: 4911.91.00 (Impressos) · 3919.90.00 (Adesivos) · 7606.11.00 (ACM/Alumínio) · 3920.49.00 (PVC/Plástico)
CódigoNome / DescriçãoNCMUnidadeValor PadrãoCFOPStatusAções
Nenhum produto fiscal cadastrado
🧪 Insumos
Matérias-primas · Custos · Estoque mínimo
Carregando insumos...
📦 Estoque
Movimentações · Saldo · Alertas
Carregando estoque...
🛒 Compras
Pedidos de compra · Recebimentos · Atualização de estoque
Carregando compras...
🏭 Fornecedores
Cadastro · Histórico de compras · Preços
Carregando fornecedores...
📋 Produtos
Produtos estruturados · Materiais padrão · Fórmulas
Carregando produtos...
📈 Relatórios
Lucro · Custos · Clientes · Produtos
Calculando relatórios...
Minha Assinatura
Planos, pagamentos e renovação
Carregando...
🏭 PCP — Controle de Produção
Planejamento · Sequenciamento · Capacidade · Eficiência
Carregando PCP...
Admin SaaS
Gestao de planos, clientes e receita
Carregando painel...
Configurações
Sistema, banco de dados e planos
🤖
Assistente IA — OpenAI
A IA usa o modelo GPT-4o-mini para analisar orçamentos, sugerir preços e calcular metalon. Sua API Key é salva somente no seu navegador (localStorage) e nunca é enviada ao nosso servidor.
⚙️
Parâmetros da IA
⚙️
Motor de Cálculo — Defaults
Estes valores são usados como padrão em novos orçamentos. Você pode alterar por orçamento.
📈
Escala de Produção
🗄️
Conexão Supabase
Não configurado
Empresa ID:
Usuário:
📋
SQL — Execute no Supabase
Database → SQL Editor → New query → Cole e execute.
-- Execute no Supabase SQL Editor
-- (10 tabelas + RLS + função multiempresa)

CREATE TABLE IF NOT EXISTS empresas (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  nome TEXT NOT NULL, email TEXT, telefone TEXT,
  cnpj TEXT, cidade TEXT, obs TEXT, logo_url TEXT,
  plano TEXT DEFAULT 'free', ativo BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS usuarios_empresa (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  role TEXT DEFAULT 'owner',
  created_at TIMESTAMPTZ DEFAULT now(),
  UNIQUE(user_id, empresa_id)
);
CREATE TABLE IF NOT EXISTS clientes (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  nome TEXT NOT NULL, telefone TEXT, email TEXT,
  cpf_cnpj TEXT, endereco TEXT, cidade TEXT, obs TEXT,
  ativo BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS materiais (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  nome TEXT NOT NULL, tipo TEXT DEFAULT 'Impressão',
  custo_m2 NUMERIC DEFAULT 0, preco_m2 NUMERIC DEFAULT 0,
  markup NUMERIC DEFAULT 0, largura_rolo NUMERIC,
  ativo BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS orcamentos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  cliente_id UUID REFERENCES clientes(id),
  numero TEXT, cliente_nome TEXT, telefone TEXT,
  produto TEXT, material TEXT,
  largura NUMERIC DEFAULT 0, altura NUMERIC DEFAULT 0,
  qtd INTEGER DEFAULT 1, area NUMERIC DEFAULT 0,
  custo_total NUMERIC DEFAULT 0, preco_final NUMERIC DEFAULT 0,
  lucro NUMERIC DEFAULT 0, margem_real NUMERIC DEFAULT 0,
  margem NUMERIC DEFAULT 40, desconto NUMERIC DEFAULT 0,
  prazo TEXT, pagamento TEXT, validade TEXT DEFAULT '7',
  obs TEXT, status TEXT DEFAULT 'orcamento',
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS itens_orcamento (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  orcamento_id UUID REFERENCES orcamentos(id) ON DELETE CASCADE,
  seq INTEGER DEFAULT 1, descricao TEXT NOT NULL,
  quantidade NUMERIC DEFAULT 1, unidade TEXT DEFAULT 'm2',
  valor_unitario NUMERIC DEFAULT 0, valor_total NUMERIC DEFAULT 0,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS pedidos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  orcamento_id UUID REFERENCES orcamentos(id),
  cliente_id UUID REFERENCES clientes(id),
  numero TEXT, cliente_nome TEXT, produto TEXT, material TEXT,
  largura NUMERIC DEFAULT 0, altura NUMERIC DEFAULT 0, area NUMERIC DEFAULT 0,
  valor NUMERIC DEFAULT 0, status TEXT DEFAULT 'producao',
  prioridade TEXT DEFAULT 'normal',
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS financeiro (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  orcamento_id UUID REFERENCES orcamentos(id),
  pedido_id UUID REFERENCES pedidos(id),
  tipo TEXT NOT NULL, categoria TEXT DEFAULT 'outros',
  descricao TEXT NOT NULL, valor NUMERIC DEFAULT 0,
  status TEXT DEFAULT 'pendente', vencimento DATE, pago_em DATE,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS notas_fiscais (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  orcamento_id UUID REFERENCES orcamentos(id),
  pedido_id UUID REFERENCES pedidos(id),
  cliente_nome TEXT, numero_nf TEXT, serie TEXT DEFAULT '1',
  chave_acesso TEXT, xml_url TEXT, pdf_url TEXT,
  valor NUMERIC DEFAULT 0, status TEXT DEFAULT 'pendente',
  resposta_api TEXT, created_at TIMESTAMPTZ DEFAULT now()
);

-- Função helper multiempresa (RLS)
CREATE OR REPLACE FUNCTION get_empresa_id()
RETURNS UUID AS $$
  SELECT empresa_id FROM usuarios_empresa
  WHERE user_id = auth.uid() LIMIT 1;
$$ LANGUAGE SQL SECURITY DEFINER STABLE;

-- Ativar RLS em todas as tabelas
ALTER TABLE empresas        ENABLE ROW LEVEL SECURITY;
ALTER TABLE usuarios_empresa ENABLE ROW LEVEL SECURITY;
ALTER TABLE clientes        ENABLE ROW LEVEL SECURITY;
ALTER TABLE materiais       ENABLE ROW LEVEL SECURITY;
ALTER TABLE orcamentos      ENABLE ROW LEVEL SECURITY;
ALTER TABLE itens_orcamento ENABLE ROW LEVEL SECURITY;
ALTER TABLE pedidos         ENABLE ROW LEVEL SECURITY;
ALTER TABLE financeiro      ENABLE ROW LEVEL SECURITY;
ALTER TABLE notas_fiscais   ENABLE ROW LEVEL SECURITY;

-- Políticas (RLS) — cada empresa vê só seus dados
DO $$ BEGIN
  DROP POLICY IF EXISTS "own_empresas" ON empresas;
  DROP POLICY IF EXISTS "own_usuarios_empresa" ON usuarios_empresa;
  DROP POLICY IF EXISTS "own_clientes" ON clientes;
  DROP POLICY IF EXISTS "own_materiais" ON materiais;
  DROP POLICY IF EXISTS "own_orcamentos" ON orcamentos;
  DROP POLICY IF EXISTS "own_itens_orcamento" ON itens_orcamento;
  DROP POLICY IF EXISTS "own_pedidos" ON pedidos;
  DROP POLICY IF EXISTS "own_financeiro" ON financeiro;
  DROP POLICY IF EXISTS "own_notas_fiscais" ON notas_fiscais;
END $$;
CREATE POLICY "own_empresas" ON empresas FOR ALL
  USING (id = get_empresa_id()) WITH CHECK (id = get_empresa_id());
CREATE POLICY "own_usuarios_empresa" ON usuarios_empresa FOR ALL
  USING (user_id = auth.uid());
CREATE POLICY "own_clientes" ON clientes FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());
CREATE POLICY "own_materiais" ON materiais FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());
CREATE POLICY "own_orcamentos" ON orcamentos FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());
CREATE POLICY "own_itens_orcamento" ON itens_orcamento FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());
CREATE POLICY "own_pedidos" ON pedidos FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());
CREATE POLICY "own_financeiro" ON financeiro FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());
CREATE POLICY "own_notas_fiscais" ON notas_fiscais FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());

-- ─── TABELAS SAAS (assinaturas, pagamentos, planos) ────────────────
CREATE TABLE IF NOT EXISTS assinaturas (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  plano TEXT DEFAULT 'free',
  status TEXT DEFAULT 'ativa',
  valor NUMERIC DEFAULT 0,
  data_inicio TIMESTAMPTZ DEFAULT now(),
  data_expiracao TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT now(),
  UNIQUE(empresa_id)
);
CREATE TABLE IF NOT EXISTS pagamentos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  assinatura_id UUID REFERENCES assinaturas(id),
  valor NUMERIC DEFAULT 0,
  metodo TEXT DEFAULT 'pix',
  status TEXT DEFAULT 'pendente',
  descricao TEXT,
  referencia TEXT,
  data_pagamento TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS planos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  nome TEXT NOT NULL UNIQUE,
  nome_interno TEXT,
  valor NUMERIC DEFAULT 0,
  cor TEXT DEFAULT '#8892a8',
  descricao TEXT,
  features JSONB,
  limite_usuarios INTEGER DEFAULT -1,
  recursos JSONB,
  ativo BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS checklist_producao (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  pedido_id UUID REFERENCES pedidos(id) ON DELETE CASCADE,
  etapa TEXT NOT NULL,
  ordem INTEGER DEFAULT 1,
  concluido BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- RLS para tabelas SaaS
ALTER TABLE assinaturas ENABLE ROW LEVEL SECURITY;
ALTER TABLE pagamentos   ENABLE ROW LEVEL SECURITY;
ALTER TABLE planos       ENABLE ROW LEVEL SECURITY;
ALTER TABLE checklist_producao ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
  DROP POLICY IF EXISTS "own_assinaturas" ON assinaturas;
  DROP POLICY IF EXISTS "own_pagamentos"  ON pagamentos;
  DROP POLICY IF EXISTS "read_planos"     ON planos;
  DROP POLICY IF EXISTS "own_checklist"   ON checklist_producao;
END $$;
CREATE POLICY "own_assinaturas" ON assinaturas FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());
CREATE POLICY "own_pagamentos" ON pagamentos FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());
CREATE POLICY "read_planos" ON planos FOR SELECT USING (true);
CREATE POLICY "own_checklist" ON checklist_producao FOR ALL
  USING (empresa_id = get_empresa_id()) WITH CHECK (empresa_id = get_empresa_id());

-- Fornecedores, Insumos, Compras (tabelas ERP v4)
CREATE TABLE IF NOT EXISTS fornecedores (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  nome TEXT NOT NULL, cnpj TEXT, telefone TEXT, email TEXT,
  cidade TEXT, prazo_entrega INTEGER DEFAULT 0,
  observacoes TEXT, ativo BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS insumos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  fornecedor_id UUID REFERENCES fornecedores(id),
  nome TEXT NOT NULL, codigo TEXT, unidade TEXT DEFAULT 'm²',
  custo_unitario NUMERIC DEFAULT 0, preco_venda NUMERIC DEFAULT 0,
  estoque_atual NUMERIC DEFAULT 0, estoque_minimo NUMERIC DEFAULT 0,
  categoria TEXT DEFAULT 'Outros', ativo BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS compras (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  fornecedor_id UUID REFERENCES fornecedores(id),
  numero TEXT, data DATE, valor_total NUMERIC DEFAULT 0,
  status TEXT DEFAULT 'pendente', nota_fiscal TEXT, observacoes TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS compra_itens (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  compra_id UUID REFERENCES compras(id) ON DELETE CASCADE,
  insumo_id UUID REFERENCES insumos(id),
  descricao TEXT, quantidade NUMERIC DEFAULT 0,
  custo_unitario NUMERIC DEFAULT 0,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS estoque_movimentos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  insumo_id UUID REFERENCES insumos(id),
  tipo TEXT NOT NULL, quantidade NUMERIC DEFAULT 0,
  custo_unitario NUMERIC DEFAULT 0, custo_total NUMERIC DEFAULT 0,
  motivo TEXT, referencia_id TEXT, referencia_tipo TEXT, observacoes TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS produtos_cat (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  nome TEXT NOT NULL, tipo TEXT DEFAULT 'Banner', descricao TEXT,
  materiais_padrao JSONB, acabamentos_pad JSONB, formula JSONB,
  ativo BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT now()
);

-- Índices de performance
CREATE INDEX IF NOT EXISTS idx_ue_user ON usuarios_empresa(user_id);
CREATE INDEX IF NOT EXISTS idx_orc_emp ON orcamentos(empresa_id, status);
CREATE INDEX IF NOT EXISTS idx_ped_emp ON pedidos(empresa_id, status);
CREATE INDEX IF NOT EXISTS idx_fin_emp ON financeiro(empresa_id, tipo, status);
CREATE INDEX IF NOT EXISTS idx_ass_emp ON assinaturas(empresa_id, status);
CREATE INDEX IF NOT EXISTS idx_pag_emp ON pagamentos(empresa_id, status);
CREATE INDEX IF NOT EXISTS idx_ins_emp ON insumos(empresa_id);
CREATE INDEX IF NOT EXISTS idx_chk_ped ON checklist_producao(pedido_id);
🔧
SQL — Corrigir Permissões + Ativar Premium
⚠️ Execute se aparecer erro 403 ou "permission denied". Também ativa o plano Avançado.
-- ═══════════════════════════════════════════════════════════════════════
-- VisualCalc ERP SaaS Pro v10 — SQL COMPLETO PRONTO PARA PRODUÇÃO
-- Execute: Supabase → SQL Editor → New query → Run (All)
-- ═══════════════════════════════════════════════════════════════════════

-- ── 1. CRIAR TABELAS QUE FALTAM ──────────────────────────────────────
CREATE TABLE IF NOT EXISTS assinaturas (
  id                    UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id            UUID REFERENCES empresas(id) ON DELETE CASCADE,
  plano                 TEXT DEFAULT 'free',
  status                TEXT DEFAULT 'ativa',
  valor                 NUMERIC DEFAULT 0,
  data_inicio           TIMESTAMPTZ DEFAULT now(),
  data_expiracao        TIMESTAMPTZ,
  stripe_customer_id    TEXT,
  stripe_subscription_id TEXT,
  stripe_price_id       TEXT,
  trial_end             TIMESTAMPTZ,
  cancel_at_period_end  BOOLEAN DEFAULT false,
  created_at            TIMESTAMPTZ DEFAULT now(),
  UNIQUE(empresa_id)
);
CREATE TABLE IF NOT EXISTS pagamentos (
  id                UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id        UUID REFERENCES empresas(id) ON DELETE CASCADE,
  assinatura_id     UUID REFERENCES assinaturas(id),
  valor             NUMERIC DEFAULT 0,
  metodo            TEXT DEFAULT 'pix',
  status            TEXT DEFAULT 'pendente',
  descricao         TEXT,
  referencia        TEXT,
  stripe_invoice_id TEXT,
  stripe_payment_id TEXT,
  data_pagamento    TIMESTAMPTZ,
  created_at        TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS planos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  nome TEXT NOT NULL UNIQUE, nome_interno TEXT,
  valor NUMERIC DEFAULT 0, cor TEXT DEFAULT '#8892a8',
  descricao TEXT, features JSONB, limite_usuarios INTEGER DEFAULT -1,
  recursos JSONB, ativo BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS checklist_producao (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  pedido_id UUID REFERENCES pedidos(id) ON DELETE CASCADE,
  etapa TEXT NOT NULL, ordem INTEGER DEFAULT 1,
  concluido BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS fornecedores (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  nome TEXT NOT NULL, cnpj TEXT, telefone TEXT, email TEXT,
  cidade TEXT, prazo_entrega INTEGER DEFAULT 0,
  observacoes TEXT, ativo BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS insumos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  fornecedor_id UUID REFERENCES fornecedores(id),
  nome TEXT NOT NULL, codigo TEXT, unidade TEXT DEFAULT 'm²',
  custo_unitario NUMERIC DEFAULT 0, preco_venda NUMERIC DEFAULT 0,
  estoque_atual NUMERIC DEFAULT 0, estoque_minimo NUMERIC DEFAULT 0,
  categoria TEXT DEFAULT 'Outros', ativo BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS compras (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  fornecedor_id UUID REFERENCES fornecedores(id),
  numero TEXT, data DATE, valor_total NUMERIC DEFAULT 0,
  status TEXT DEFAULT 'pendente', nota_fiscal TEXT, observacoes TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS compra_itens (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  compra_id UUID REFERENCES compras(id) ON DELETE CASCADE,
  insumo_id UUID REFERENCES insumos(id),
  descricao TEXT, quantidade NUMERIC DEFAULT 0,
  custo_unitario NUMERIC DEFAULT 0, created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS estoque_movimentos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  insumo_id UUID REFERENCES insumos(id),
  tipo TEXT NOT NULL, quantidade NUMERIC DEFAULT 0,
  custo_unitario NUMERIC DEFAULT 0, custo_total NUMERIC DEFAULT 0,
  motivo TEXT, referencia_id TEXT, referencia_tipo TEXT,
  observacoes TEXT, created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS produtos_cat (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  empresa_id UUID REFERENCES empresas(id) ON DELETE CASCADE,
  nome TEXT NOT NULL, tipo TEXT DEFAULT 'Banner', descricao TEXT,
  materiais_padrao JSONB, acabamentos_pad JSONB, formula JSONB,
  ativo BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT now()
);

-- ── 2. GARANTIR COLUNAS ────────────────────────────────────────────
ALTER TABLE assinaturas ADD COLUMN IF NOT EXISTS valor NUMERIC DEFAULT 0;
ALTER TABLE assinaturas ADD COLUMN IF NOT EXISTS stripe_customer_id TEXT;
ALTER TABLE assinaturas ADD COLUMN IF NOT EXISTS stripe_subscription_id TEXT;
ALTER TABLE assinaturas ADD COLUMN IF NOT EXISTS stripe_price_id TEXT;
ALTER TABLE assinaturas ADD COLUMN IF NOT EXISTS trial_end TIMESTAMPTZ;
ALTER TABLE assinaturas ADD COLUMN IF NOT EXISTS cancel_at_period_end BOOLEAN DEFAULT false;
ALTER TABLE pagamentos  ADD COLUMN IF NOT EXISTS stripe_invoice_id TEXT;
ALTER TABLE pagamentos  ADD COLUMN IF NOT EXISTS stripe_payment_id TEXT;
ALTER TABLE assinaturas ADD COLUMN IF NOT EXISTS data_inicio TIMESTAMPTZ DEFAULT now();
ALTER TABLE assinaturas ADD COLUMN IF NOT EXISTS data_expiracao TIMESTAMPTZ;
ALTER TABLE pagamentos ADD COLUMN IF NOT EXISTS metodo TEXT DEFAULT 'pix';
ALTER TABLE pagamentos ADD COLUMN IF NOT EXISTS descricao TEXT;
ALTER TABLE pagamentos ADD COLUMN IF NOT EXISTS data_pagamento TIMESTAMPTZ;
ALTER TABLE pagamentos ADD COLUMN IF NOT EXISTS referencia TEXT;
ALTER TABLE pagamentos ADD COLUMN IF NOT EXISTS assinatura_id UUID;
ALTER TABLE planos ADD COLUMN IF NOT EXISTS features JSONB;
ALTER TABLE planos ADD COLUMN IF NOT EXISTS nome_interno TEXT;
ALTER TABLE planos ADD COLUMN IF NOT EXISTS cor TEXT DEFAULT '#8892a8';

-- ── 3. FUNÇÃO get_empresa_id() ────────────────────────────────────
CREATE OR REPLACE FUNCTION get_empresa_id()
RETURNS UUID LANGUAGE sql STABLE SECURITY DEFINER AS $$
  SELECT empresa_id FROM usuarios_empresa WHERE user_id = auth.uid() LIMIT 1;
$$;

-- ── 4. PERMISSÕES ──────────────────────────────────────────────────
GRANT USAGE ON SCHEMA public TO authenticated, anon;
GRANT SELECT, INSERT, UPDATE ON empresas, usuarios_empresa, assinaturas, pagamentos TO authenticated;
GRANT SELECT, INSERT, UPDATE, DELETE ON orcamentos, itens_orcamento, clientes, materiais, pedidos, financeiro, notas_fiscais, fornecedores, insumos, compras, compra_itens, estoque_movimentos, produtos_cat, checklist_producao TO authenticated;
GRANT SELECT ON planos TO authenticated;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;

-- ── 5. RLS — TODAS AS TABELAS ──────────────────────────────────────
DO $$
DECLARE t TEXT;
BEGIN
  -- Desabilitar + Limpar + Recriar para cada tabela
  FOREACH t IN ARRAY ARRAY[
    'empresas','usuarios_empresa','assinaturas','pagamentos',
    'orcamentos','itens_orcamento','clientes','materiais','pedidos',
    'financeiro','notas_fiscais','fornecedores','insumos',
    'compras','compra_itens','estoque_movimentos','produtos_cat',
    'checklist_producao'
  ]
  LOOP
    EXECUTE format('ALTER TABLE %I DISABLE ROW LEVEL SECURITY', t);
    EXECUTE format('DROP POLICY IF EXISTS "own_%1$s" ON %1$I', t);
    EXECUTE format('DROP POLICY IF EXISTS "select_%1$s" ON %1$I', t);
    EXECUTE format('DROP POLICY IF EXISTS "insert_%1$s" ON %1$I', t);
    EXECUTE format('DROP POLICY IF EXISTS "update_%1$s" ON %1$I', t);
    EXECUTE format('DROP POLICY IF EXISTS "read_%1$s" ON %1$I', t);
  END LOOP;
END;
$$;

-- RLS: Empresas (pode inserir nova, só vê a própria)
ALTER TABLE empresas DISABLE ROW LEVEL SECURITY;
CREATE POLICY "select_empresas" ON empresas FOR SELECT USING (id = get_empresa_id());
CREATE POLICY "insert_empresas" ON empresas FOR INSERT WITH CHECK (true);
CREATE POLICY "update_empresas" ON empresas FOR UPDATE USING (id = get_empresa_id());
ALTER TABLE empresas ENABLE ROW LEVEL SECURITY;

-- RLS: usuarios_empresa
ALTER TABLE usuarios_empresa DISABLE ROW LEVEL SECURITY;
CREATE POLICY "own_usuarios_empresa" ON usuarios_empresa FOR ALL
  USING (empresa_id = get_empresa_id() OR user_id = auth.uid())
  WITH CHECK (empresa_id = get_empresa_id());
ALTER TABLE usuarios_empresa ENABLE ROW LEVEL SECURITY;

-- RLS: planos (leitura pública)
ALTER TABLE planos DISABLE ROW LEVEL SECURITY;
CREATE POLICY "read_planos" ON planos FOR SELECT USING (true);
ALTER TABLE planos ENABLE ROW LEVEL SECURITY;

-- RLS: todas as outras tabelas — padrão empresa_id
DO $$
DECLARE t TEXT;
BEGIN
  FOREACH t IN ARRAY ARRAY[
    'assinaturas','pagamentos','orcamentos','itens_orcamento',
    'clientes','materiais','pedidos','financeiro','notas_fiscais',
    'fornecedores','insumos','compras','compra_itens',
    'estoque_movimentos','produtos_cat','checklist_producao'
  ]
  LOOP
    EXECUTE format(
      'CREATE POLICY "own_%1$s" ON %1$I FOR ALL
       USING (empresa_id = get_empresa_id())
       WITH CHECK (empresa_id = get_empresa_id())',
      t
    );
    EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', t);
  END LOOP;
END;
$$;

-- ── 6. REMOVER ASSINATURAS DUPLICADAS ─────────────────────────────
DELETE FROM assinaturas WHERE id NOT IN (
  SELECT DISTINCT ON (empresa_id) id FROM assinaturas
  ORDER BY empresa_id, created_at DESC
);

-- ── 7. UPSERT PLANOS ───────────────────────────────────────────────
INSERT INTO planos (nome, nome_interno, valor, cor, features) VALUES
  ('Básico',   'free',    0,   '#8892a8', '{"modulos":["dashboard","orcamento","historico","clientes","materiais"]}'::jsonb),
  ('Médio',    'pro',     97,  '#e8b84b', '{"modulos":["dashboard","orcamento","historico","clientes","materiais","pedidos","financeiro","insumos","estoque","compras","fornecedores","produtos"]}'::jsonb),
  ('Avançado', 'premium', 197, '#8b5cf6', '{"modulos":["dashboard","orcamento","historico","clientes","materiais","pedidos","financeiro","insumos","estoque","compras","fornecedores","produtos","relatorios","nf","pcp"]}'::jsonb)
ON CONFLICT (nome) DO UPDATE SET
  nome_interno = EXCLUDED.nome_interno, valor = EXCLUDED.valor,
  cor = EXCLUDED.cor, features = EXCLUDED.features;

-- ── 8. ATIVAR PREMIUM NA SUA CONTA (OWNER) ──────────────────────
ALTER TABLE empresas DISABLE ROW LEVEL SECURITY;
UPDATE empresas SET plano = 'premium';
ALTER TABLE empresas ENABLE ROW LEVEL SECURITY;

INSERT INTO assinaturas (empresa_id, plano, status, valor, data_inicio, data_expiracao)
SELECT id, 'premium', 'ativa', 0, now(), now() + interval '3650 days'
FROM empresas
ON CONFLICT (empresa_id) DO UPDATE SET
  plano = 'premium', status = 'ativa',
  data_expiracao = now() + interval '3650 days';

-- ── 9. FUNÇÃO trocar_plano() ──────────────────────────────────────
CREATE OR REPLACE FUNCTION trocar_plano(novo_plano TEXT)
RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$
DECLARE emp_id UUID := get_empresa_id();
BEGIN
  UPDATE empresas SET plano = novo_plano WHERE id = emp_id;
  INSERT INTO assinaturas (empresa_id, plano, status, data_inicio, data_expiracao)
  VALUES (emp_id, novo_plano, 'ativa', now(), now() + interval '30 days')
  ON CONFLICT (empresa_id) DO UPDATE SET
    plano = novo_plano, status = 'ativa',
    data_expiracao = now() + interval '30 days';
END;
$$;

-- ── 10. ÍNDICES ────────────────────────────────────────────────────
CREATE INDEX IF NOT EXISTS idx_ue_user ON usuarios_empresa(user_id);
CREATE INDEX IF NOT EXISTS idx_orc_emp ON orcamentos(empresa_id, status);
CREATE INDEX IF NOT EXISTS idx_ped_emp ON pedidos(empresa_id, status);
CREATE INDEX IF NOT EXISTS idx_fin_emp ON financeiro(empresa_id, tipo, status);
CREATE INDEX IF NOT EXISTS idx_ass_emp ON assinaturas(empresa_id, status);
CREATE INDEX IF NOT EXISTS idx_pag_emp ON pagamentos(empresa_id, status);
CREATE INDEX IF NOT EXISTS idx_ins_emp ON insumos(empresa_id);
CREATE INDEX IF NOT EXISTS idx_chk_ped ON checklist_producao(pedido_id);

-- ── 11. FUNÇÃO AUXILIAR: owner lê todas empresas ─────────────────
-- Necessária para o painel Admin SaaS do owner
CREATE OR REPLACE FUNCTION is_owner_user()
RETURNS BOOLEAN LANGUAGE sql STABLE SECURITY DEFINER AS $$
  SELECT EXISTS (
    SELECT 1 FROM usuarios_empresa
    WHERE user_id = auth.uid() AND role = 'owner'
  );
$$;

-- Política adicional: owner pode ver todas assinaturas e pagamentos
DO $$ BEGIN
  DROP POLICY IF EXISTS "owner_all_assinaturas" ON assinaturas;
  DROP POLICY IF EXISTS "owner_all_pagamentos"  ON pagamentos;
  DROP POLICY IF EXISTS "owner_all_empresas"    ON empresas;
END $$;
CREATE POLICY "owner_all_assinaturas" ON assinaturas FOR SELECT
  USING (is_owner_user() OR empresa_id = get_empresa_id());
CREATE POLICY "owner_all_pagamentos" ON pagamentos FOR SELECT
  USING (is_owner_user() OR empresa_id = get_empresa_id());
CREATE POLICY "owner_all_empresas" ON empresas FOR SELECT
  USING (is_owner_user() OR id = get_empresa_id());

-- ── 12. FUNÇÃO pode_acessar_modulo (validação server-side) ──────────
-- Chamável via RPC do frontend: supabase.rpc('pode_acessar_modulo', {modulo:'pcp'})
-- NÃO depende do frontend. Valida plano no banco usando auth.uid().
CREATE OR REPLACE FUNCTION pode_acessar_modulo(modulo TEXT)
RETURNS BOOLEAN LANGUAGE plpgsql SECURITY DEFINER AS $$
DECLARE
  v_empresa_id UUID;
  v_role       TEXT;
  v_plano      TEXT;
  v_status     TEXT;
  v_expiracao  TIMESTAMPTZ;
BEGIN
  -- Buscar empresa e role do usuário autenticado
  SELECT ue.empresa_id, ue.role
  INTO   v_empresa_id, v_role
  FROM   usuarios_empresa ue
  WHERE  ue.user_id = auth.uid()
  LIMIT  1;

  IF v_empresa_id IS NULL THEN RETURN FALSE; END IF;

  -- Owner: acesso total (definido no banco, não no frontend)
  IF v_role = 'owner' THEN RETURN TRUE; END IF;

  -- Buscar assinatura ativa
  SELECT a.plano, a.status, a.data_expiracao
  INTO   v_plano, v_status, v_expiracao
  FROM   assinaturas a
  WHERE  a.empresa_id = v_empresa_id
    AND  a.status     = 'ativa'
  ORDER  BY a.created_at DESC
  LIMIT  1;

  -- Sem assinatura ativa: bloquear
  IF v_plano IS NULL OR v_status != 'ativa' THEN RETURN FALSE; END IF;

  -- Assinatura expirada: bloquear
  IF v_expiracao IS NOT NULL AND v_expiracao < NOW() THEN RETURN FALSE; END IF;

  -- Regras por módulo e plano
  RETURN CASE modulo
    WHEN 'dashboard'    THEN TRUE
    WHEN 'orcamento'    THEN TRUE
    WHEN 'historico'    THEN TRUE
    WHEN 'clientes'     THEN TRUE
    WHEN 'materiais'    THEN TRUE
    WHEN 'config'       THEN TRUE
    WHEN 'pedidos'      THEN v_plano IN ('medio','pro','avancado','premium')
    WHEN 'financeiro'   THEN v_plano IN ('medio','pro','avancado','premium')
    WHEN 'insumos'      THEN v_plano IN ('medio','pro','avancado','premium')
    WHEN 'estoque'      THEN v_plano IN ('medio','pro','avancado','premium')
    WHEN 'compras'      THEN v_plano IN ('medio','pro','avancado','premium')
    WHEN 'fornecedores' THEN v_plano IN ('medio','pro','avancado','premium')
    WHEN 'produtos'     THEN v_plano IN ('medio','pro','avancado','premium')
    WHEN 'relatorios'   THEN v_plano IN ('avancado','premium')
    WHEN 'nf'           THEN v_plano IN ('avancado','premium')
    WHEN 'pcp'          THEN v_plano IN ('avancado','premium')
    ELSE FALSE
  END;
END;
$$;

-- Permissão: usuários autenticados podem chamar a função
GRANT EXECUTE ON FUNCTION pode_acessar_modulo(TEXT) TO authenticated;

-- ── 13. TABELA logs_seguranca ─────────────────────────────────────
CREATE TABLE IF NOT EXISTS logs_seguranca (
  id         UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id    UUID REFERENCES auth.users(id),
  empresa_id UUID REFERENCES empresas(id),
  acao       TEXT NOT NULL,
  detalhes   JSONB,
  ip         TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_log_user ON logs_seguranca(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_log_acao ON logs_seguranca(acao, created_at DESC);
ALTER TABLE logs_seguranca ENABLE ROW LEVEL SECURITY;
-- Usuário só vê seus próprios logs; owner vê todos
DROP POLICY IF EXISTS "own_logs" ON logs_seguranca;
CREATE POLICY "own_logs" ON logs_seguranca FOR ALL
  USING (user_id = auth.uid() OR is_owner_user());

-- ── 14. FUNÇÕES SQL DE MÉTRICAS (usadas via RPC) ──────────────────
-- MRR: soma das assinaturas ativas com valor > 0
CREATE OR REPLACE FUNCTION calc_mrr()
RETURNS NUMERIC LANGUAGE sql STABLE SECURITY DEFINER AS $$
  SELECT COALESCE(SUM(valor), 0)
  FROM   assinaturas
  WHERE  status = 'ativa'
    AND  (data_expiracao IS NULL OR data_expiracao > NOW());
$$;

-- Churn Rate: % de assinaturas canceladas/vencidas
CREATE OR REPLACE FUNCTION calc_churn()
RETURNS NUMERIC LANGUAGE sql STABLE SECURITY DEFINER AS $$
  SELECT CASE
    WHEN COUNT(*) = 0 THEN 0
    ELSE ROUND(
      COUNT(*) FILTER (WHERE status IN ('cancelada','vencida'))::NUMERIC
      / COUNT(*)::NUMERIC * 100, 1
    )
  END
  FROM assinaturas;
$$;

-- Novos clientes no mês atual
CREATE OR REPLACE FUNCTION calc_novos_mes()
RETURNS INTEGER LANGUAGE sql STABLE SECURITY DEFINER AS $$
  SELECT COUNT(*)::INTEGER
  FROM   empresas
  WHERE  created_at >= date_trunc('month', NOW());
$$;

-- Receita do mês (pagamentos confirmados)
CREATE OR REPLACE FUNCTION calc_receita_mes()
RETURNS NUMERIC LANGUAGE sql STABLE SECURITY DEFINER AS $$
  SELECT COALESCE(SUM(valor), 0)
  FROM   pagamentos
  WHERE  status = 'pago'
    AND  created_at >= date_trunc('month', NOW());
$$;

-- Verificar e vencer assinaturas expiradas (chamada via cron ou manualmente)
CREATE OR REPLACE FUNCTION verificar_assinaturas_expiradas()
RETURNS INTEGER LANGUAGE plpgsql SECURITY DEFINER AS $$
DECLARE
  v_count INTEGER;
BEGIN
  UPDATE assinaturas
  SET    status = 'vencida'
  WHERE  status = 'ativa'
    AND  data_expiracao IS NOT NULL
    AND  data_expiracao < NOW();
  GET DIAGNOSTICS v_count = ROW_COUNT;
  RETURN v_count;
END;
$$;

-- Permissões para as funções de métricas
GRANT EXECUTE ON FUNCTION calc_mrr()                        TO authenticated;
GRANT EXECUTE ON FUNCTION calc_churn()                      TO authenticated;
GRANT EXECUTE ON FUNCTION calc_novos_mes()                  TO authenticated;
GRANT EXECUTE ON FUNCTION calc_receita_mes()                TO authenticated;
GRANT EXECUTE ON FUNCTION verificar_assinaturas_expiradas() TO authenticated;

-- ── 15. BLOQUEAR UPDATE direto em assinaturas (só via função) ─────
-- Remove permissão de UPDATE direto para usuários comuns
-- (só service_role pode atualizar; frontend usa funções SECURITY DEFINER)
DROP POLICY IF EXISTS "no_direct_update_assinaturas" ON assinaturas;
CREATE POLICY "no_direct_update_assinaturas" ON assinaturas FOR UPDATE
  USING (is_owner_user()); -- só owner pode UPDATE direto; outros via função

-- ── 16. VERIFICAR RESULTADO ────────────────────────────────────────
SELECT e.nome, e.plano, a.plano AS ass_plano, a.status, a.data_expiracao::date AS expira
FROM empresas e LEFT JOIN assinaturas a ON a.empresa_id = e.id
ORDER BY a.created_at DESC LIMIT 10;
💡 "Ativar Avançado AGORA" funciona imediatamente sem banco. Execute o SQL para tornar permanente.
🏢
Dados da Empresa
💎 Plano atual: Free
Free
R$ 0/mês
Para começar
✅ 50 orçamentos/mês
✅ 1 usuário
✅ Histórico 30 dias
❌ Clientes ilimitados
❌ Relatórios avançados
Pro ✨
R$ 97/mês
Para negócios em crescimento
✅ Orçamentos ilimitados
✅ Clientes ilimitados
✅ Histórico completo
✅ 3 usuários
✅ Relatórios PDF
Premium 🚀
R$ 197/mês
Para equipes e escala
✅ Tudo do Pro
✅ Usuários ilimitados
✅ API access
✅ Nota Fiscal integrada
✅ Suporte prioritário
💾
Exportar Dados
⬇️
Restaurar Backup
⚠️ Substituirá todos os dados locais.
📖 Central de Ajuda
Guias, tutoriais e instruções do VisualCalc ERP
Carregando tutoriais...
👥 Equipe
Gestão de usuários, permissões e acessos
🔐
Matriz de Permissões
Módulo Admin Vendas Produção Financeiro
Dashboard
Orçamentos
Clientes
Produção
Financeiro
Configurações
Carregando equipe...