# TechXCar — Design Spec **Data:** 2026-06-16 **Versão:** 1.0 --- ## 1. Visão Geral **TechXCar** é um SaaS multi-tenant de gestão de oficina automóvel. Cada oficina tem o seu espaço isolado com clientes, veículos, ordens de trabalho, faturação, técnicos e relatórios. A plataforma é gerida por um super-admin que cria e convida oficinas manualmente. **Inspiração:** LubeLogger, mas focado na gestão operacional da oficina (não no diário do proprietário do veículo). --- ## 2. Stack Técnica | Camada | Tecnologia | |---|---| | Frontend | React 19 + TypeScript + Vite | | Backend | Go + Fiber (framework HTTP) | | Base de dados | PostgreSQL 16 | | Cache / Filas | Redis 7 | | Auth | JWT (access 15min + refresh 30d, httpOnly cookie) | | Passwords | bcrypt cost 12 | | PDF | Geração server-side em Go (html/template → PDF) | | Notificações | Telegram Bot API + SMTP | | Frontend UI | Tailwind CSS 4 + shadcn/ui | | Estado cliente | TanStack Query + Zustand | | Formulários | React Hook Form + Zod | | Roteamento | React Router v7 | | Migrations | golang-migrate | | Deploy | Docker Compose → Coolify | --- ## 3. Arquitectura de Deploy ### Serviços Docker ``` techxcar/ ├── frontend/ # React 19 + TypeScript + Vite (servido via Nginx) ├── backend/ # Go + Fiber ├── docker-compose.yml # Desenvolvimento └── docker-compose.prod.yml # Produção (Coolify) ``` Quatro serviços: - `frontend` — Nginx a servir o build Vite na porta 80 - `backend` — Binário Go na porta 8080 - `postgres` — PostgreSQL 16 - `redis` — Redis 7 ### Configuração em dois níveis 1. **`.env`** — valores de arranque imutáveis em runtime: `DATABASE_URL`, `REDIS_URL`, `JWT_SECRET`, `PORT`, `APP_ENV`. Requerem reinício do container para alterar. 2. **`platform_settings` / `tenant_settings` (DB)** — configuração dinâmica gerida pela Web UI sem redeploy: SMTP, Telegram Bot Token, templates de notificação, limites, dados de faturação. Cached em Redis por 5 minutos. ### Coolify - Secrets geridos pelo Coolify (injectados como variáveis de ambiente) - HTTPS automático via Coolify (Let's Encrypt) - Health checks em `/api/v1/health` --- ## 4. Multi-tenancy ### Estratégia: Schema-per-tenant no PostgreSQL - Schema `public`: tabelas globais (`tenants`, `invites`, `super_admins`, `platform_settings`) - Schema `tenant_`: dados isolados de cada oficina - Middleware no backend define `SET search_path = tenant_` por request, com base no JWT ### Onboarding de oficinas **Via convite:** Super-admin gera token assinado com expiração configurável. Oficina clica no link, preenche nome, NIF, email, password → schema PostgreSQL provisionado automaticamente via migrations. **Via criação manual:** Super-admin cria a oficina no painel `/admin`, define credenciais iniciais, envia email de boas-vindas. --- ## 5. Autenticação & Autorização ### Níveis de acesso ``` super_admin — gestão da plataforma, todos os tenants tenant_admin — dono/gestor principal da oficina manager — gestor operacional, sem acesso a settings financeiras technician — técnico interno, acesso a OTs atribuídas external_technician — sem login; apenas para atribuição e registo de custos ``` ### Fluxo JWT - `POST /api/v1/auth/login` → devolve `access_token` (body) + `refresh_token` (httpOnly cookie) - `POST /api/v1/auth/refresh` → renova access token usando refresh token do cookie - `POST /api/v1/auth/logout` → invalida refresh token - Rate limiting: 10 tentativas/min por IP nas rotas de auth (Redis) --- ## 6. Módulos de Negócio ### 6.1 Clientes & Veículos - **Cliente**: nome, NIF, telefone, email, morada, notas - **Veículo**: pertence a um cliente (opcional), matrícula, marca, modelo, ano, VIN, km actual - Um cliente pode ter N veículos - OT pode ser criada sem cliente nem veículo (cliente temporário) ### 6.2 Ordens de Trabalho (OT) **Estados e transições:** ``` Aberta → Em Progresso → Concluída → Faturada ↓ Cancelada (de qualquer estado excepto Faturada) ``` **Conteúdo de uma OT:** - Cliente + Veículo (ambos opcionais) - Items: serviços/peças do catálogo (qty, preço unitário, desconto por item) - Técnico(s) atribuído(s) + registo de horas (horas × custo/hora) - Notas internas (não visíveis ao cliente) - Notas para o cliente (aparecem no PDF) - Log de estados: timestamp + utilizador responsável por cada transição - Total calculado: soma de items + mão de obra ### 6.3 Catálogo de Serviços & Peças - Item: código, nome, categoria, unidade (un/hora/litro/kg), preço base, activo/inactivo - Categorias livres (criadas pela oficina) - Ao adicionar a OT: preço copiado do catálogo mas editável por OT - Pesquisa por código ou nome no momento de adição à OT ### 6.4 Faturação & Orçamentos - **Orçamento**: gerado a partir de OT em estado "Aberta", PDF com logo e dados da oficina - **Fatura**: gerada quando OT passa a "Faturada", numeração sequencial por oficina - PDFs gerados server-side em Go com **`chromedp`** (render HTML → PDF via Chrome headless); armazenados localmente (volume Docker) ou S3-compatible (configurável nas Settings) - Dados da oficina para PDF: logo, nome, NIF, morada, IBAN — configuráveis nas Settings da Web UI ### 6.5 Técnicos (Staff) - **Interno**: tem conta no sistema (role `technician`), aparece na atribuição de OTs - **Externo**: registo simplificado (nome, contacto, custo/hora), sem login - Registo de horas por OT: horas trabalhadas × custo/hora = custo de mão de obra - Listagem com estado activo/inactivo ### 6.6 Combustíveis & Despesas da Oficina - Registo: data, tipo (combustível / peças / ferramentas / outros), valor, descrição, veículo (opcional) - Filtros por período e tipo - Totais agregados no dashboard e relatórios ### 6.7 Notificações ao Cliente - **Telegram**: token do bot configurável nas Settings; envia mensagem quando OT muda para "Concluída" ou "Faturada" - **Email**: SMTP configurável nas Settings; template HTML editável na Web UI - Configurável por oficina: quais eventos disparam notificação e qual canal - Envio assíncrono: o handler HTTP publica um job numa lista Redis; uma goroutine dedicada consome a lista e envia as notificações (não bloqueia o request HTTP) ### 6.8 Dashboard & Relatórios **Dashboard (tempo real):** - Receita do mês actual vs mês anterior - Nº de OTs por estado (cards) - OTs abertas recentes (tabela) - Serviços mais realizados (top 5) **Relatórios (com filtro de período):** - Receita por período (dia/semana/mês) - OTs por estado e por técnico - Despesas vs Receita - Exportação em PDF e CSV --- ## 7. Estrutura do Backend Go ``` backend/ ├── cmd/server/main.go ├── internal/ │ ├── auth/ # JWT, middleware, bcrypt │ ├── tenant/ # Gestão tenants, schema provisioning │ ├── workorder/ # Ordens de trabalho │ ├── client/ # Clientes & veículos │ ├── catalog/ # Serviços & peças │ ├── invoice/ # Faturação, geração PDF │ ├── staff/ # Técnicos │ ├── expense/ # Combustíveis & despesas │ ├── notification/ # Telegram + Email (worker assíncrono) │ ├── report/ # Relatórios & exportações │ └── settings/ # Settings dinâmicas (DB + cache Redis) ├── pkg/ │ ├── database/ # Pool PostgreSQL, migrations │ ├── redis/ # Cliente Redis │ └── pdf/ # Geração PDF └── migrations/ ├── public/ # Schema público └── tenant/ # Schema tenant (aplicado no provisionamento) ``` ### API - Base: `/api/v1/` - Autenticação: Bearer token no header `Authorization` - Tenant resolvido via claim `tenant_id` no JWT - Respostas: `{ "data": ..., "error": null, "meta": { "page": ..., "total": ... } }` - Paginação cursor-based --- ## 8. Estrutura do Frontend React ``` frontend/ ├── src/ │ ├── pages/ │ │ ├── auth/ # Login │ │ ├── admin/ # Super-admin (dashboard, tenants, settings) │ │ └── app/ # Área da oficina │ │ ├── dashboard/ │ │ ├── work-orders/ │ │ ├── clients/ │ │ ├── vehicles/ │ │ ├── catalog/ │ │ ├── staff/ │ │ ├── expenses/ │ │ ├── reports/ │ │ └── settings/ │ ├── components/ │ │ ├── ui/ # shadcn/ui base components │ │ └── shared/ # Componentes reutilizáveis da app │ ├── hooks/ # Custom hooks (useAuth, useTenant, etc.) │ ├── lib/ │ │ ├── api.ts # Cliente HTTP (fetch + interceptors) │ │ └── queryClient.ts # TanStack Query config │ ├── store/ # Zustand stores │ └── i18n/ # Estrutura i18n (PT por defeito, EN preparado) ``` ### Rotas protegidas - `/login` — público - `/admin/*` — requer role `super_admin` - `/app/*` — requer role `tenant_admin`, `manager` ou `technician` - Guards no React Router v7 redireccionam para `/login` se JWT expirado --- ## 9. Base de Dados — Schemas ### Schema `public` ```sql tenants (id uuid PK, slug text UNIQUE, name text, status, plan, created_at) invites (id uuid PK, tenant_id uuid FK, token text UNIQUE, expires_at, used_at nullable) super_admins (id uuid PK, email text UNIQUE, password_hash text, created_at) platform_settings (key text PK, value text, updated_at) ``` ### Schema `tenant_` (por oficina) ```sql users (id uuid PK, email, password_hash, role, name, active) clients (id uuid PK, name, nif, phone, email, address, notes, created_at) vehicles (id uuid PK, client_id uuid nullable FK, plate, brand, model, year, vin, mileage, notes) work_orders (id uuid PK, number serial, client_id nullable, vehicle_id nullable, status, internal_notes, client_notes, created_by, created_at, updated_at) wo_items (id uuid PK, work_order_id FK, catalog_item_id nullable FK, description, qty, unit_price, discount_pct, total) wo_staff_hours (id uuid PK, work_order_id FK, staff_id FK, hours, cost_per_hour, total) wo_status_log (id uuid PK, work_order_id FK, from_status, to_status, changed_by FK, changed_at) catalog_items (id uuid PK, code, name, category, unit, base_price, active) invoices (id uuid PK, work_order_id FK, type: quote|invoice, number serial, pdf_path, issued_at) staff (id uuid PK, user_id nullable FK, name, email, phone, type: internal|external, hourly_rate, active) expenses (id uuid PK, vehicle_id nullable FK, type, amount, description, date) tenant_settings (key text PK, value text, updated_at) ``` --- ## 10. Internacionalização (i18n) - Estrutura i18n no frontend desde o início (ficheiros JSON por locale em `src/i18n/`) - Idioma padrão e único no MVP: **Português de Portugal (pt-PT)** - Inglês (en) preparado na estrutura mas não traduzido no MVP - Datas, moeda e números formatados com `Intl` API (locale `pt-PT`, moeda `EUR`) --- ## 11. Segurança - HTTPS obrigatório em produção (Coolify / Let's Encrypt) - JWT com expiração curta + refresh token rotativo em httpOnly cookie - Passwords com bcrypt cost 12 - Rate limiting em todas as rotas de auth (Redis) - Row-level isolation via PostgreSQL `search_path` por tenant - Headers de segurança: CSP, HSTS, X-Frame-Options (middleware Fiber) - Inputs validados com Zod (frontend) e struct validation em Go (backend) - Ficheiros PDF armazenados fora do webroot, servidos via endpoint autenticado --- ## 12. Fora de Âmbito (MVP) - Billing automático / Stripe - App mobile nativa - Integração com sistemas de diagnóstico OBD - Portal self-service para clientes da oficina - Multi-idioma completo (EN traduzido) - Backups automáticos (responsabilidade do Coolify/infra)