Files
techxcar/docs/superpowers/specs/2026-06-16-techxcar-design.md
Luciano Milani 5de37bb512 Inicial
2026-07-02 12:47:55 +01:00

297 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_<uuid>`: dados isolados de cada oficina
- Middleware no backend define `SET search_path = tenant_<uuid>` 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_<uuid>` (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)