As tuas faturas entram no sistema sozinhas.
SaaS de processamento automático de faturas para empresas portuguesas. A fatura entra (upload ou email dedicado), a IA lê e estrutura os dados, o utilizador valida numa tela lado a lado, e exporta em CSV/JSON/SAF-T — com fornecedores, estados de pagamento e alertas de vencimento incluídos.
| Camada | Tecnologia |
|---|---|
| Framework | Next.js 14 (App Router) + TypeScript |
| Estilo | Tailwind CSS · família UNREAL (Bricolage Grotesque + Hanken Grotesk + JetBrains Mono), papel claro, assinatura teal |
| Base de dados / Auth / Storage | Supabase (PostgreSQL com RLS) |
| Extração de faturas | API Anthropic (Claude com visão) |
| Pagamentos | Stripe (subscrições Lite / Pro / Business) |
| Email transacional | Resend |
| Deploy | Vercel |
- Node.js 18+ (testado com Node 24)
- Uma conta Supabase (ou o Supabase CLI para correr localmente)
- Uma chave da API Anthropic
- (Opcional para testar pagamentos) conta Stripe
- (Opcional para emails) conta Resend
npm installcp .env.example .env.localPreenche pelo menos:
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY,SUPABASE_SERVICE_ROLE_KEY— painel Supabase → Project Settings → APIANTHROPIC_API_KEY— sem isto as faturas ficam no estado «erro» ao processar
As restantes (Stripe, Resend, email inbound) são opcionais em desenvolvimento — a aplicação funciona sem elas.
Opção A — Supabase Cloud (mais rápido):
No painel Supabase → SQL Editor, corre por ordem:
supabase/migrations/0001_schema_inicial.sqlsupabase/migrations/0002_storage.sqlsupabase/migrations/0003_nivel1_robustez.sql(linhas de artigo, IVA por taxa, retenção, fila de email, auditoria)supabase/migrations/0004_nivel2_memoria.sql(categoria de despesa, memória por fornecedor)supabase/migrations/0005_nivel3_integracoes.sql(ligações a ERP — credenciais Moloni)supabase/migrations/0006_memoria_v2.sql(identidade do fornecedor: nome canónico, NIFs e valores aprendidos)supabase/migrations/0007_dados_fiscais.sql(morada da empresa para o Header do SAF-T)supabase/migrations/0008_aprovacao_reconciliacao.sql(aprovação de pagamento por admin, referência e comprovativo)supabase/migrations/0009_equipa_e_politica.sql(equipa por empresa, papel dono, política de aprovação; correções de RLS)supabase/migrations/0010_role_constraint_defensivo.sql(garantia do constraint de papel)supabase/migrations/0011_rate_limits.sql(rate limiting com store partilhado)supabase/migrations/0012_pipeline_custo.sql(dedup por hash, degrau e custo por documento, modo lote)supabase/migrations/0013_camada_contabilistas.sql(firmas: clientes, operadores, RLS aditiva por acesso)supabase/migrations/0014_hardening_rpc.sql(guarda de acesso nas RPC de uso/rate-limit)
Opção B — Supabase local:
supabase start
supabase db reset # aplica migrations + seed automaticamenteCom o seed local ficas com uma conta demo: [email protected] / demo1234, com fornecedores e faturas em vários estados.
O
supabase/seed.sqlinsere diretamente emauth.users— é só para desenvolvimento local, nunca o corras em produção.
npm run devAbre http://localhost:3000, cria conta (ou entra com a demo), dá o nome e NIF da empresa, e arrasta a primeira fatura.
- Cria três produtos com preço mensal recorrente (Lite, Pro, Business) e copia
os Price IDs para
STRIPE_PRICE_LITE,STRIPE_PRICE_PRO,STRIPE_PRICE_BUSINESS. Os valores de montra mostrados na aplicação configuram-se em lib/plans.ts. - Cria um webhook apontado a
https://<o-teu-dominio>/api/webhooks/stripecom os eventoscheckout.session.completed,customer.subscription.*einvoice.payment_failed; copia o segredo paraSTRIPE_WEBHOOK_SECRET. - O período de teste (14 dias) configura-se em
STRIPE_TRIAL_DAYS.
Para testar localmente: stripe listen --forward-to localhost:3000/api/webhooks/stripe.
Cada organização recebe um slug único (email_slug). O webhook
/api/webhooks/email-inbound?segredo=<INBOUND_EMAIL_SECRET> aceita:
- multipart/form-data — SendGrid Inbound Parse, Mailgun
- JSON com anexos base64 — Postmark, CloudMailin
Configura no teu fornecedor de email inbound um catch-all para
*.unrealdocs.pt (ou o domínio que definires em INBOUND_EMAIL_DOMAIN) a
apontar para esse endpoint. A organização é identificada pelo destinatário
(faturas@<slug>.<domínio>).
- Importa o repositório na Vercel (framework: Next.js, sem configuração extra).
- Define todas as variáveis do
.env.exampleno projeto. - Atualiza
NEXT_PUBLIC_APP_URLpara o domínio final. - No Supabase, adiciona
https://<o-teu-dominio>/auth/callbackaos Redirect URLs (Authentication → URL Configuration).
app/
page.tsx Landing page
(auth)/login, registo Autenticação (password + magic link)
onboarding/ Criação da organização (nome + NIF)
(app)/
dashboard/ Métricas, alertas de vencimento, evolução mensal
faturas/ Lista com filtros e pesquisa
faturas/[id]/ Tela de validação lado a lado (o coração do produto)
fornecedores/ Lista e histórico por fornecedor
exportar/ CSV / JSON / SAF-T com seleção múltipla
integracoes/ Email dedicado, pasta vigiada, conectores ERP
definicoes/ Empresa, plano, utilização, Stripe
api/
invoices/upload Upload + processamento IA
invoices/[id] Correções, validação, pagamento, remoção
invoices/[id]/reprocessar
exports/ Geração de ficheiros de exportação
stripe/checkout, portal
webhooks/stripe Sincronização de subscrições
webhooks/email-inbound Entrada de faturas por email
onboarding/ Criação de organização (service role)
lib/
extraction.ts Prompt e chamada à API Claude (visão)
processing.ts Pipeline: storage → IA → validações → usage
validation.ts NIF (dígito de controlo), totais, anomalias
exporters.ts CSV, JSON e esqueleto SAF-T
plans.ts Planos, limites e preços (configuráveis)
integrations.ts Registo de conectores ERP plugáveis
supabase/
migrations/ Schema + RLS + storage
seed.sql Dados de exemplo (apenas local)
- RLS em todas as tabelas, isolada por
org_idvia funçãocurrent_org_id(). - Storage privado com políticas por organização; pré-visualizações servidas por URLs assinadas com validade de 1 hora.
- A chave de service role é usada apenas no servidor (onboarding e webhooks).
- Webhook de email protegido por segredo partilhado; webhook Stripe verificado por assinatura.
| Plano | Faturas/mês | Preço de montra |
|---|---|---|
| Lite | 100 | 29 €/mês |
| Pro | 400 | 79 €/mês |
| Business | 1000 | 179 €/mês |
A contagem vive na tabela usage (por organização e mês) e é incrementada
atomicamente a cada processamento. Ao atingir 90% o utilizador é avisado; a 100%
o processamento é bloqueado até mudar de plano.