Changelog
Todas as mudancas notaveis deste projeto serao documentadas neste arquivo.
O formato segue Keep a Changelog,
e o projeto adere ao Versionamento Semantico.
[Unreleased]
Adicionado
- Modulo
Integrations— handoff sincrono do XP Lead Engine (docs/INTEGRATIONS.md §2.4)
- POST /api/v1/integrations/lead-engine/leads (service token; tenant vem do token)
- Cria/atualiza Contact (idempotente por e-mail, update nao-destrutivo),
Account (find-or-create por nome se o lead trouxer empresa),
Opportunity (primeiro stage do pipeline default; nao duplica open existente)
e Activity nota "Vindo do XP Lead Engine" (idempotente por lead_id)
- Migracao
038_service_tokens.sql— tabela esperada peloServiceTokenMiddleware
(ja referenciada pelo provisionamento Hub → CRM, mas ausente das migracoes)
- Script
scripts/create-service-token.php— gera/revoga service tokens (hash SHA-256) ContactsRepository::findByEmail()
Corrigido
ServiceTokenMiddlewareagora respeitaexpires_atdo token
Planejado
- Phase 2: Products, Quotations, Contracts, Email/Calendar sync
- Rate limit + CORS (M2.5)
- 2FA TOTP, recuperacao de senha por email, convite de usuario
[0.6.0] - 2026-04-25 — Activities + Dashboard (M6)
Adicionado
- Modulo
Activitiescom 7 endpoints REST (CRUD + restore + complete)
- 8 tipos suportados: call, email, meeting, task, note, sms, whatsapp, linkedin
- Vinculo polimorfico (contact OU account OU opportunity, com CHECK no DB)
- Campos prontos para sync externo: external_id, external_source
- Metadata JSONB livre por tipo (call: phone/sentiment, email: opens/clicks)
- Filtros: ?type, ?status, ?owner_id=me, ?overdue, ?due_from/to, etc.
- Endpoint atalho POST /activities/{id}/complete
- Modulo
Dashboardcom 3 endpoints read-only
- GET /dashboard/summary — 12 KPIs em uma query (contatos, accounts, pipeline aberto + weighted, won_this_month, conversion_rate, avg_deal_cycle, activities_today/overdue)
- GET /dashboard/pipeline — kanban-ready, stages com open/won/lost counts e values
- GET /dashboard/activity-feed — timeline com nomes joinados (owner, contact, account, opp)
- Migracao
017_activities_audit.sql— trigger de audit emactivities
Corrigido
- ORDER BY em activities: posicao do
NULLS LASTestava antes do ASC/DESC
Documentacao
docs/IMPLEMENTED.md— visao geral das funcionalidades + comparativo com HubSpot, Pipedrive, Salesforce, RD Station
[0.5.0] - 2026-04-25 — Pipelines + Opportunities (M5)
Adicionado
- Modulo
Pipelines(10 endpoints)
- CRUD de pipelines + flag is_default + restore
- CRUD aninhado de stages: /pipelines/{id}/stages e /pipelines/{id}/stages/{stageId}
- Seed automatico de 6 stages padrao no POST /pipelines (Prospecting → Qualified → Proposal → Negotiation → Closed Won → Closed Lost)
- Bloqueia delete de stage com opportunities ativas (409)
- Modulo
Opportunities(8 endpoints)
- CRUD com FK validation (account, pipeline, stage no pipeline, owner)
- Endpoint especial POST /opportunities/{id}/move:
- Insere em opportunity_stage_history com duration_in_from_stage
- Atualiza stage_id, stage_entered_at, probability (default do novo stage)
- Auto-detecta is_won/is_lost do stage destino → seta status, won_at/lost_at, closed_at
- Re-abertura de stage fechado limpa timestamps e volta status='open'
- GET /opportunities/{id}/history retorna timeline
- 15+ filtros incluindo faixa de valor e datas de fechamento
- Migracao
016_opp_stage_history_rls.sql— habilita RLS na tabela de historico (estava desprotegida)
Diferenca arquitetural
- Services de Opportunities NAO chamam
AuditService::log()manualmente — confiam na trigger de audit ja existente. Para acoes nao-default (delete/restore), usamDatabase::setAuditAction('delete')antes da operacao para o trigger registrar com a label correta.
Resolvido
- Bug
Ambiguous parameterno INSERT deopportunity_stage_historyquandostage_entered_atera NULL (caso da insercao inicial). Refatorado para dois caminhos SQL distintos.
[0.4.0] - 2026-04-25 — Accounts + Contacts (M4)
Adicionado
- Modulo
Accounts(6 endpoints)
- 20+ campos: nome, legal_name, tax_id, industry, size, tier, website, telefone
- Hierarquia matriz/filial via parent_account_id
- Enderecos JSONB (billing + shipping), tags TEXT[], custom_fields JSONB
- Lifecycle stage (prospect/customer/churned/evangelist) + tier (A/B/C) + health_score 0-100
- Filtros: ?q, ?lifecycle_stage, ?tier, ?industry, ?owner_id, ?country
- Modulo
Contacts(6 endpoints)
- 30+ campos incluindo full_name GENERATED ALWAYS, dedupe_hash SHA-256 auto-computado
- LGPD/RGPD: consent_marketing, consent_data_processing, do_not_contact, unsubscribed_at, unsubscribe_reason
- UTM tracking completo
- Lifecycle (lead → mql → sql → opportunity → customer) + lead_status + temperatura + score
- Migracao
015_audit_trigger_with_actor.sql— redefineaudit_trigger_function():
- Le app.current_user_id da sessao para popular audit_logs.user_id
- Computa changed_fields[] automaticamente em UPDATEs (via jsonb_each)
- Suporta override de action via app.current_action (para registrar 'delete'/'restore' em vez de 'update')
- Adicionado em
Database:
- setActor(?string $userId) — chamado pelo AuthMiddleware apos identificar o user
- setAuditAction(?string $action) — usado por services para customizar a action
Resolvido
- DNS collision no nginx interno —
app:9000resolvia paraassistants-app(redeproxycompartilhada). Corrigido paraxp-crm-app:9000. Documentado em memoria.
[0.3.0] - 2026-04-25 — Users (M3)
Adicionado
- Modulo
Users(7 endpoints)
- CRUD com role gating (tenant_admin / manager / sales / csm / read_only)
- Self-update com lista branca de campos (first_name, last_name, locale, timezone, phone, mobile, avatar_url)
- Self-protection: usuario nao pode deletar a si mesmo, nem alterar propria role/is_active
- Soft delete + restore
- POST /users/{id}/password — admin direto OU self com current_password obrigatorio
- Adicionado em
BaseController:
- requireRole(...$roles) — 403 se user nao tiver role permitida
- pagination() — clamping de page/per_page
Resolvido
- Coluna
deleted_byreferenciada porDatabase::softDeletemas inexistente no schema. Removida — actor fica emaudit_logs.
[0.2.0] - 2026-04-25 — Auth + Tenancy (M2)
Adicionado
JwtService(firebase/php-jwt HS256, access 15min + refresh 7d, claims sub/tnt/rol/typ/jti)AuditService::log()— escreve em audit_logs com diff de camposBaseControllercom helpers (currentUser, ok, fail, etc.)AuthMiddleware— Bearer token, valida JWT, carrega user via RLS, anexa em requestTenantMiddleware— re-bind doDatabase::setTenant()apos auth- Modulo
Auth(5 endpoints):register,login,refresh,logout,me
- Lockout apos 5 falhas (15 min) com flags failed_login_attempts e locked_until
- Rehash automatico em login se password_needs_rehash (atualizacao de algoritmo)
- Audit em todos os eventos: register, login, login_failed, login_locked, logout
- Modulo
Tenants(interno, sem rotas publicas) — usado pelo register - Migracoes:
- 011_force_rls.sql — FORCE ROW LEVEL SECURITY em 9 tabelas
- 013_app_role.sql — cria role xp_app (LOGIN, NOSUPERUSER) e GRANTs DML
- 014_audit_logs_insert_policy.sql — adiciona policy de INSERT (so SELECT existia → bloqueava o app)
- App passa a conectar como
xp_app(nao maisxp_usersuperuser). DB_USERNAME atualizado em.env.xp_userreservado para migrations DDL.
Resolvido (3 armadilhas RLS criticas, registradas em memoria)
1. xp_user era SUPERUSER (default da imagem pgvector/pgvector:pg16) — superusers bypassam RLS sempre. Solucao: criar xp_app separado.
2. RLS sem FORCE permite ao OWNER da tabela bypassar. Solucao: ALTER TABLE ... FORCE ROW LEVEL SECURITY.
3. audit_logs so tinha policy FOR SELECT — INSERT do app era bloqueado. Solucao: adicionar policy de INSERT explicita.
[0.1.5] - 2026-04-25 — Foundation Framework (M1)
Adicionado
- Front controller
public/index.phpcom inicializacao Dotenv + sessao src/Core/:
- Application (singleton, kernel, pipeline de middleware, error handler)
- Container (DI com auto-wiring via reflection)
- Router (grupos, middlewares, params tipados {id:uuid}, {n:int})
- Request / Response (security headers, JSON helpers)
- Database (PDO singleton + savepoints + RLS via setTenant())
src/Helpers/functions.php— env(), config(), uuid(), is_valid_uuid(), e(), logger(), t()routes/api.phpcom/api/v1/ping(liveness) e/api/v1/health(DB + Redis + Meili)routes/web.phpcom home placeholder- Estrutura de modulos auto-descoberta (
src/Modules/<Mod>/routes.phpe auto-carregado)
Resolvido (armadilhas registradas)
- Imagem
postgres:16-alpinenao tempgvector— migrations falhavam silenciosamente. Trocada parapgvector/pgvector:pg16. Dotenv::safeLoad()nao popula$_ENVpara chaves ja emgetenv()(vindas dodocker-compose environment). Solucao: usar sempre o helperenv().- Permissoes de log: container
www(UID 1000) nao escrevia emlogs/ownuead pordeploy(UID 1002). Compose passaHOST_UID/HOST_GIDcomo build args.
Infraestrutura
- 5 containers Docker em producao no VPS:
- xp-crm-nginx (rede proxy + xp-crm-net)
- xp-crm-app (PHP 8.2-FPM com extensoes pdo_pgsql, redis, intl, gd, opcache)
- xp-crm-db (PostgreSQL 16 + pgvector)
- xp-crm-redis (Redis 7 com password)
- xp-crm-meili (Meilisearch 1.6)
- Portas: 9006 (nginx publico), 5437/6382/7700 (DB/Redis/Meili em 127.0.0.1 only)
- HTTPS publico em
https://crm.xpevolution.frvia Nginx Proxy Manager + Let's Encrypt - Migrations 001-010 aplicadas (15 tabelas)
[0.1.0] - 2026-04-15
Adicionado
- Estrutura inicial do projeto
- Documentacao fundacional:
- README.md - Visao geral do produto
- CLAUDE.md - Configuracao para Claude Code
- docs/ARCHITECTURE.md - Arquitetura tecnica detalhada
- docs/SCHEMA.md - Modelo de dados completo
- docs/API.md - Especificacao REST/GraphQL
- docs/SECURITY.md - Diretrizes de seguranca (OWASP, LGPD/RGPD)
- docs/WORKPLAN.md - Plano de trabalho em sprints (18 meses)
- docs/PRODUCT_VISION.md - Visao, publico e posicionamento
- docs/ROADMAP.md - Roadmap de releases
- docs/INTEGRATIONS.md - Integracoes com ecossistema XP
- docs/EVENT_BUS.md - Especificacao do barramento de eventos
- docs/DEPLOYMENT.md - Guia de deploy
- docs/DEVELOPMENT_PITFALLS.md - Armadilhas comuns
- docs/CODING_STANDARDS.md - Padroes de codigo
- Estrutura de pastas seguindo padrao do ecossistema XP
- Primeiras migracoes SQL (001_init, 002_tenants, 003_users)
- Arquivos de configuracao inicial