nestforge-generator 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ This guide validates a project generated from the NestForge Prisma template.
8
8
 
9
9
  * Node.js 20 or later
10
10
  * npm 10 or later
11
- * Docker when testing PostgreSQL, MySQL, Redis, or Mailpit
11
+ * Docker when testing PostgreSQL, MySQL, MongoDB, Redis, or Mailpit
12
12
 
13
13
  SQLite can be tested without Docker.
14
14
 
@@ -30,10 +30,18 @@ npm run prisma:generate
30
30
 
31
31
  ## Apply the development schema
32
32
 
33
+ For PostgreSQL, MySQL, or SQLite:
34
+
33
35
  ```bash
34
36
  npm run prisma:migrate -- --name init
35
37
  ```
36
38
 
39
+ For MongoDB:
40
+
41
+ ```bash
42
+ npm run prisma:push
43
+ ```
44
+
37
45
  Run the seed when the generated project includes password authentication:
38
46
 
39
47
  ```bash
@@ -67,7 +75,7 @@ Unit tests mock Prisma and Redis, so they do not require real services.
67
75
  npm run test:e2e
68
76
  ```
69
77
 
70
- The `pretest:e2e` script runs `prisma migrate deploy` with `.env.test` before the suite starts.
78
+ The `pretest:e2e` script runs `prisma migrate deploy` for relational databases or `prisma db push` for MongoDB with `.env.test` before the suite starts.
71
79
 
72
80
  Depending on the generated authentication strategy, the suite may cover:
73
81
 
@@ -110,11 +118,25 @@ npm test
110
118
  npm run test:e2e
111
119
  ```
112
120
 
121
+ ### MongoDB
122
+
123
+ MongoDB must run as a replica set because Prisma uses transactions for nested writes. The generated Docker Compose configures a single-node replica set:
124
+
125
+ ```bash
126
+ docker compose up -d mongodb
127
+ npm run prisma:push
128
+ npm run build
129
+ npm test
130
+ npm run test:e2e
131
+ ```
132
+
133
+ Use different database names in `.env` and `.env.test`, such as `nestforge` and `nestforge_test`.
134
+
113
135
  ## Final checklist
114
136
 
115
137
  * [ ] Dependencies install successfully
116
138
  * [ ] Prisma Client is generated
117
- * [ ] Migrations are applied
139
+ * [ ] Migrations are applied, or the MongoDB schema is pushed
118
140
  * [ ] Seed runs when applicable
119
141
  * [ ] Build passes
120
142
  * [ ] Lint passes
@@ -8,7 +8,7 @@ Este guia valida um projeto gerado a partir do template Prisma do NestForge.
8
8
 
9
9
  * Node.js 20 ou superior
10
10
  * npm 10 ou superior
11
- * Docker para testar PostgreSQL, MySQL, Redis ou Mailpit
11
+ * Docker para testar PostgreSQL, MySQL, MongoDB, Redis ou Mailpit
12
12
 
13
13
  SQLite pode ser testado sem Docker.
14
14
 
@@ -30,10 +30,18 @@ npm run prisma:generate
30
30
 
31
31
  ## Aplicar o schema de desenvolvimento
32
32
 
33
+ Para PostgreSQL, MySQL ou SQLite:
34
+
33
35
  ```bash
34
36
  npm run prisma:migrate -- --name init
35
37
  ```
36
38
 
39
+ Para MongoDB:
40
+
41
+ ```bash
42
+ npm run prisma:push
43
+ ```
44
+
37
45
  Execute o seed quando o projeto gerado incluir autenticação por senha:
38
46
 
39
47
  ```bash
@@ -67,7 +75,7 @@ Os testes unitários simulam Prisma e Redis, portanto não exigem serviços reai
67
75
  npm run test:e2e
68
76
  ```
69
77
 
70
- O script `pretest:e2e` executa `prisma migrate deploy` com `.env.test` antes do início da suíte.
78
+ O script `pretest:e2e` executa `prisma migrate deploy` nos bancos relacionais ou `prisma db push` no MongoDB com `.env.test` antes do início da suíte.
71
79
 
72
80
  Dependendo da estratégia de autenticação gerada, a suíte pode cobrir:
73
81
 
@@ -110,11 +118,25 @@ npm test
110
118
  npm run test:e2e
111
119
  ```
112
120
 
121
+ ### MongoDB
122
+
123
+ O MongoDB deve funcionar como replica set porque o Prisma usa transações em escritas aninhadas. O Docker Compose gerado configura um replica set de nó único:
124
+
125
+ ```bash
126
+ docker compose up -d mongodb
127
+ npm run prisma:push
128
+ npm run build
129
+ npm test
130
+ npm run test:e2e
131
+ ```
132
+
133
+ Use nomes de banco diferentes em `.env` e `.env.test`, como `nestforge` e `nestforge_test`.
134
+
113
135
  ## Checklist final
114
136
 
115
137
  * [ ] As dependências são instaladas corretamente
116
138
  * [ ] O Prisma Client é gerado
117
- * [ ] As migrations são aplicadas
139
+ * [ ] As migrations são aplicadas ou o schema MongoDB é enviado
118
140
  * [ ] O seed executa quando aplicável
119
141
  * [ ] O build passa
120
142
  * [ ] O lint passa
@@ -10,6 +10,10 @@ services:
10
10
  - "3000:3000"
11
11
  env_file:
12
12
  - .env
13
+ # nestforge:feature:database:mongodb
14
+ environment:
15
+ DATABASE_URL: mongodb://mongodb:27017/nestforge?replicaSet=rs0
16
+ # nestforge:feature:database:mongodb:end
13
17
  depends_on:
14
18
  # nestforge:feature:database:postgres
15
19
  - postgres
@@ -17,6 +21,9 @@ services:
17
21
  # nestforge:feature:database:mysql
18
22
  - mysql
19
23
  # nestforge:feature:database:mysql:end
24
+ # nestforge:feature:database:mongodb
25
+ - mongodb
26
+ # nestforge:feature:database:mongodb:end
20
27
  - redis
21
28
  volumes:
22
29
  - ./src:/app/src
@@ -53,6 +60,23 @@ services:
53
60
  - mysql_data:/var/lib/mysql
54
61
  # nestforge:feature:database:mysql:end
55
62
 
63
+ # nestforge:feature:database:mongodb
64
+ mongodb:
65
+ image: mongo:8
66
+ container_name: nestforge-mongodb
67
+ restart: unless-stopped
68
+ command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
69
+ ports:
70
+ - "27017:27017"
71
+ volumes:
72
+ - mongodb_data:/data/db
73
+ healthcheck:
74
+ test: ["CMD-SHELL", "mongosh --quiet --eval \"try { rs.status().ok } catch (error) { rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'mongodb:27017'}]}).ok }\" | grep 1"]
75
+ interval: 5s
76
+ timeout: 5s
77
+ retries: 10
78
+ # nestforge:feature:database:mongodb:end
79
+
56
80
  redis:
57
81
  image: redis:7-alpine
58
82
  container_name: nestforge-redis
@@ -75,4 +99,8 @@ volumes:
75
99
  # nestforge:feature:database:mysql
76
100
  volumes:
77
101
  mysql_data:
78
- # nestforge:feature:database:mysql:end
102
+ # nestforge:feature:database:mysql:end
103
+ # nestforge:feature:database:mongodb
104
+ volumes:
105
+ mongodb_data:
106
+ # nestforge:feature:database:mongodb:end
@@ -1,230 +1,246 @@
1
- # Como adicionar um novo módulo
2
-
3
- Este guia mostra o passo a passo pra adicionar um recurso novo seguindo as convenções do NestForge, usando um módulo `posts` (posts de blog) como exemplo. Adapte os nomes pro seu caso.
4
-
5
- ## 1. Adicione o model no Prisma
6
-
7
- Em `prisma/schema.prisma`:
8
-
9
- ```prisma
10
- model Post {
11
- id String @id @default(uuid())
12
- title String
13
- content String
14
- authorId String
15
- author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
16
- createdAt DateTime @default(now())
17
- updatedAt DateTime @updatedAt
18
-
19
- @@map("posts")
20
- }
21
- ```
22
-
23
- Não esqueça de adicionar a relação inversa no `model User`:
24
-
25
- ```prisma
26
- posts Post[]
27
- ```
28
-
29
- Gere a migration:
30
-
31
- ```bash
32
- npx prisma migrate dev --name add_posts
33
- ```
34
-
35
- ## 2. Crie a estrutura de pastas
36
-
37
- ```
38
- src/posts/
39
- ├── dto/
40
- │ ├── create-post.dto.ts
41
- │ └── update-post.dto.ts
42
- ├── entities/
43
- │ └── post.entity.ts
44
- ├── posts.controller.ts
45
- ├── posts.service.ts
46
- └── posts.module.ts
47
- ```
48
-
49
- ## 3. DTOs com Zod
50
-
51
- `src/posts/dto/create-post.dto.ts`:
52
-
53
- ```ts
54
- import { z } from 'zod';
55
- import { createZodDto } from 'nestjs-zod';
56
-
57
- export const createPostSchema = z.object({
58
- title: z.string().min(3).describe('Título do post'),
59
- content: z.string().min(10).describe('Conteúdo do post'),
60
- });
61
-
62
- export class CreatePostDto extends createZodDto(createPostSchema) {}
63
- ```
64
-
65
- `src/posts/dto/update-post.dto.ts`:
66
-
67
- ```ts
68
- import { createZodDto } from 'nestjs-zod';
69
- import { createPostSchema } from './create-post.dto';
70
-
71
- export const updatePostSchema = createPostSchema.partial();
72
-
73
- export class UpdatePostDto extends createZodDto(updatePostSchema) {}
74
- ```
75
-
76
- ## 4. Entity (o que a API expõe)
77
-
78
- `src/posts/entities/post.entity.ts` mesmo se não houver nada sensível pra esconder agora, criar a entity já deixa o padrão pronto pra quando houver:
79
-
80
- ```ts
81
- export class PostEntity {
82
- id: string;
83
- title: string;
84
- content: string;
85
- authorId: string;
86
- createdAt: Date;
87
- updatedAt: Date;
88
-
89
- constructor(partial: Partial<PostEntity>) {
90
- Object.assign(this, partial);
91
- }
92
- }
93
- ```
94
-
95
- ## 5. Service (regra de negócio)
96
-
97
- `src/posts/posts.service.ts`:
98
-
99
- ```ts
100
- import { Injectable, NotFoundException } from '@nestjs/common';
101
- import { PrismaService } from '../database/prisma.service';
102
- import { CreatePostDto } from './dto/create-post.dto';
103
- import { UpdatePostDto } from './dto/update-post.dto';
104
- import { PostEntity } from './entities/post.entity';
105
-
106
- @Injectable()
107
- export class PostsService {
108
- constructor(private readonly prisma: PrismaService) {}
109
-
110
- async create(authorId: string, dto: CreatePostDto) {
111
- const post = await this.prisma.post.create({ data: { ...dto, authorId } });
112
- return new PostEntity(post);
113
- }
114
-
115
- async findOne(id: string) {
116
- const post = await this.prisma.post.findUnique({ where: { id } });
117
- if (!post) throw new NotFoundException('Post não encontrado');
118
- return new PostEntity(post);
119
- }
120
-
121
- async update(id: string, dto: UpdatePostDto) {
122
- await this.findOne(id);
123
- const post = await this.prisma.post.update({ where: { id }, data: dto });
124
- return new PostEntity(post);
125
- }
126
-
127
- async remove(id: string) {
128
- await this.findOne(id);
129
- await this.prisma.post.delete({ where: { id } });
130
- return { message: 'Post removido com sucesso' };
131
- }
132
- }
133
- ```
134
-
135
- ## 6. Controller (guards + permissions)
136
-
137
- Se o recurso precisa de controle de acesso, adicione a permission em `src/common/constants/permissions.ts` e no mapeamento `src/common/constants/role-permissions.ts` antes de usar:
138
-
139
- ```ts
140
- // permissions.ts
141
- export enum Permission {
142
- // ...existentes
143
- PostCreate = 'post:create',
144
- PostDelete = 'post:delete',
145
- }
146
- ```
147
-
148
- `src/posts/posts.controller.ts`:
149
-
150
- ```ts
151
- import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
152
- import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
153
- import { PostsService } from './posts.service';
154
- import { CreatePostDto } from './dto/create-post.dto';
155
- import { UpdatePostDto } from './dto/update-post.dto';
156
- import { Permissions } from '../common/decorators/permissions.decorator';
157
- import { Permission } from '../common/constants/permissions';
158
- import { CurrentUser } from '../common/decorators/current-user.decorator';
159
-
160
- @ApiTags('posts')
161
- @ApiBearerAuth()
162
- @Controller('posts')
163
- export class PostsController {
164
- constructor(private readonly postsService: PostsService) {}
165
-
166
- @Post()
167
- @Permissions(Permission.PostCreate)
168
- create(@CurrentUser() user: { id: string }, @Body() dto: CreatePostDto) {
169
- return this.postsService.create(user.id, dto);
170
- }
171
-
172
- @Get(':id')
173
- findOne(@Param('id') id: string) {
174
- return this.postsService.findOne(id);
175
- }
176
-
177
- @Patch(':id')
178
- update(@Param('id') id: string, @Body() dto: UpdatePostDto) {
179
- return this.postsService.update(id, dto);
180
- }
181
-
182
- @Delete(':id')
183
- @Permissions(Permission.PostDelete)
184
- remove(@Param('id') id: string) {
185
- return this.postsService.remove(id);
186
- }
187
- }
188
- ```
189
-
190
- ## 7. Module
191
-
192
- `src/posts/posts.module.ts`:
193
-
194
- ```ts
195
- import { Module } from '@nestjs/common';
196
- import { PostsService } from './posts.service';
197
- import { PostsController } from './posts.controller';
198
-
199
- @Module({
200
- controllers: [PostsController],
201
- providers: [PostsService],
202
- exports: [PostsService],
203
- })
204
- export class PostsModule {}
205
- ```
206
-
207
- Registre no `src/app.module.ts` (dentro do array `imports`):
208
-
209
- ```ts
210
- import { PostsModule } from './posts/posts.module';
211
- // ...
212
- PostsModule,
213
- ```
214
-
215
- ## 8. Testes
216
-
217
- - **Unitário** (`src/posts/posts.service.spec.ts`): mocke o `PrismaService` como em `src/users/users.service.spec.ts` — sem banco real.
218
- - **E2e** (`test/posts.e2e-spec.ts`): use os helpers de `test/utils/e2e-setup.ts` e `test/utils/clean-database.ts` (adicione `prisma.post.deleteMany()` na limpeza) e siga o padrão de `test/users.e2e-spec.ts`.
219
-
220
- ## Checklist rápido
221
-
222
- - [ ] Model no `schema.prisma` + migration
223
- - [ ] DTOs com Zod (`createZodDto`)
224
- - [ ] Entity (mesmo sem campo sensível ainda)
225
- - [ ] Service sem lógica no controller
226
- - [ ] Permissions novas cadastradas em `permissions.ts` e `role-permissions.ts`, se necessário
227
- - [ ] Module registrado no `AppModule`
228
- - [ ] Teste unitário do service
229
- - [ ] Teste e2e do fluxo principal
230
- - [ ] Atualizar `ROADMAP.md` se o módulo fechar um item do roadmap
1
+ # Como adicionar um novo módulo
2
+
3
+ Este guia mostra o passo a passo pra adicionar um recurso novo seguindo as convenções do NestForge, usando um módulo `posts` (posts de blog) como exemplo. Adapte os nomes pro seu caso.
4
+
5
+ ## 1. Adicione o model no Prisma
6
+
7
+ Em `prisma/schema.prisma`:
8
+
9
+ ```prisma
10
+ model Post {
11
+ // nestforge:feature:database:relational
12
+ id String @id @default(uuid())
13
+ // nestforge:feature:database:relational:end
14
+ // nestforge:feature:database:mongodb
15
+ id String @id @default(auto()) @map("_id") @db.ObjectId
16
+ // nestforge:feature:database:mongodb:end
17
+ title String
18
+ content String
19
+ // nestforge:feature:database:relational
20
+ authorId String
21
+ // nestforge:feature:database:relational:end
22
+ // nestforge:feature:database:mongodb
23
+ authorId String @db.ObjectId
24
+ // nestforge:feature:database:mongodb:end
25
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
26
+ createdAt DateTime @default(now())
27
+ updatedAt DateTime @updatedAt
28
+
29
+ @@map("posts")
30
+ }
31
+ ```
32
+
33
+ Não esqueça de adicionar a relação inversa no `model User`:
34
+
35
+ ```prisma
36
+ posts Post[]
37
+ ```
38
+
39
+ Em PostgreSQL, MySQL ou SQLite, gere a migration:
40
+
41
+ ```bash
42
+ npx prisma migrate dev --name add_posts
43
+ ```
44
+
45
+ Em MongoDB, envie o schema diretamente:
46
+
47
+ ```bash
48
+ npm run prisma:push
49
+ ```
50
+
51
+ ## 2. Crie a estrutura de pastas
52
+
53
+ ```
54
+ src/posts/
55
+ ├── dto/
56
+ │ ├── create-post.dto.ts
57
+ │ └── update-post.dto.ts
58
+ ├── entities/
59
+ │ └── post.entity.ts
60
+ ├── posts.controller.ts
61
+ ├── posts.service.ts
62
+ └── posts.module.ts
63
+ ```
64
+
65
+ ## 3. DTOs com Zod
66
+
67
+ `src/posts/dto/create-post.dto.ts`:
68
+
69
+ ```ts
70
+ import { z } from 'zod';
71
+ import { createZodDto } from 'nestjs-zod';
72
+
73
+ export const createPostSchema = z.object({
74
+ title: z.string().min(3).describe('Título do post'),
75
+ content: z.string().min(10).describe('Conteúdo do post'),
76
+ });
77
+
78
+ export class CreatePostDto extends createZodDto(createPostSchema) {}
79
+ ```
80
+
81
+ `src/posts/dto/update-post.dto.ts`:
82
+
83
+ ```ts
84
+ import { createZodDto } from 'nestjs-zod';
85
+ import { createPostSchema } from './create-post.dto';
86
+
87
+ export const updatePostSchema = createPostSchema.partial();
88
+
89
+ export class UpdatePostDto extends createZodDto(updatePostSchema) {}
90
+ ```
91
+
92
+ ## 4. Entity (o que a API expõe)
93
+
94
+ `src/posts/entities/post.entity.ts` — mesmo se não houver nada sensível pra esconder agora, criar a entity já deixa o padrão pronto pra quando houver:
95
+
96
+ ```ts
97
+ export class PostEntity {
98
+ id: string;
99
+ title: string;
100
+ content: string;
101
+ authorId: string;
102
+ createdAt: Date;
103
+ updatedAt: Date;
104
+
105
+ constructor(partial: Partial<PostEntity>) {
106
+ Object.assign(this, partial);
107
+ }
108
+ }
109
+ ```
110
+
111
+ ## 5. Service (regra de negócio)
112
+
113
+ `src/posts/posts.service.ts`:
114
+
115
+ ```ts
116
+ import { Injectable, NotFoundException } from '@nestjs/common';
117
+ import { PrismaService } from '../database/prisma.service';
118
+ import { CreatePostDto } from './dto/create-post.dto';
119
+ import { UpdatePostDto } from './dto/update-post.dto';
120
+ import { PostEntity } from './entities/post.entity';
121
+
122
+ @Injectable()
123
+ export class PostsService {
124
+ constructor(private readonly prisma: PrismaService) {}
125
+
126
+ async create(authorId: string, dto: CreatePostDto) {
127
+ const post = await this.prisma.post.create({ data: { ...dto, authorId } });
128
+ return new PostEntity(post);
129
+ }
130
+
131
+ async findOne(id: string) {
132
+ const post = await this.prisma.post.findUnique({ where: { id } });
133
+ if (!post) throw new NotFoundException('Post não encontrado');
134
+ return new PostEntity(post);
135
+ }
136
+
137
+ async update(id: string, dto: UpdatePostDto) {
138
+ await this.findOne(id);
139
+ const post = await this.prisma.post.update({ where: { id }, data: dto });
140
+ return new PostEntity(post);
141
+ }
142
+
143
+ async remove(id: string) {
144
+ await this.findOne(id);
145
+ await this.prisma.post.delete({ where: { id } });
146
+ return { message: 'Post removido com sucesso' };
147
+ }
148
+ }
149
+ ```
150
+
151
+ ## 6. Controller (guards + permissions)
152
+
153
+ Se o recurso precisa de controle de acesso, adicione a permission em `src/common/constants/permissions.ts` e no mapeamento `src/common/constants/role-permissions.ts` antes de usar:
154
+
155
+ ```ts
156
+ // permissions.ts
157
+ export enum Permission {
158
+ // ...existentes
159
+ PostCreate = 'post:create',
160
+ PostDelete = 'post:delete',
161
+ }
162
+ ```
163
+
164
+ `src/posts/posts.controller.ts`:
165
+
166
+ ```ts
167
+ import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
168
+ import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
169
+ import { PostsService } from './posts.service';
170
+ import { CreatePostDto } from './dto/create-post.dto';
171
+ import { UpdatePostDto } from './dto/update-post.dto';
172
+ import { Permissions } from '../common/decorators/permissions.decorator';
173
+ import { Permission } from '../common/constants/permissions';
174
+ import { CurrentUser } from '../common/decorators/current-user.decorator';
175
+
176
+ @ApiTags('posts')
177
+ @ApiBearerAuth()
178
+ @Controller('posts')
179
+ export class PostsController {
180
+ constructor(private readonly postsService: PostsService) {}
181
+
182
+ @Post()
183
+ @Permissions(Permission.PostCreate)
184
+ create(@CurrentUser() user: { id: string }, @Body() dto: CreatePostDto) {
185
+ return this.postsService.create(user.id, dto);
186
+ }
187
+
188
+ @Get(':id')
189
+ findOne(@Param('id') id: string) {
190
+ return this.postsService.findOne(id);
191
+ }
192
+
193
+ @Patch(':id')
194
+ update(@Param('id') id: string, @Body() dto: UpdatePostDto) {
195
+ return this.postsService.update(id, dto);
196
+ }
197
+
198
+ @Delete(':id')
199
+ @Permissions(Permission.PostDelete)
200
+ remove(@Param('id') id: string) {
201
+ return this.postsService.remove(id);
202
+ }
203
+ }
204
+ ```
205
+
206
+ ## 7. Module
207
+
208
+ `src/posts/posts.module.ts`:
209
+
210
+ ```ts
211
+ import { Module } from '@nestjs/common';
212
+ import { PostsService } from './posts.service';
213
+ import { PostsController } from './posts.controller';
214
+
215
+ @Module({
216
+ controllers: [PostsController],
217
+ providers: [PostsService],
218
+ exports: [PostsService],
219
+ })
220
+ export class PostsModule {}
221
+ ```
222
+
223
+ Registre no `src/app.module.ts` (dentro do array `imports`):
224
+
225
+ ```ts
226
+ import { PostsModule } from './posts/posts.module';
227
+ // ...
228
+ PostsModule,
229
+ ```
230
+
231
+ ## 8. Testes
232
+
233
+ - **Unitário** (`src/posts/posts.service.spec.ts`): mocke o `PrismaService` como em `src/users/users.service.spec.ts` — sem banco real.
234
+ - **E2e** (`test/posts.e2e-spec.ts`): use os helpers de `test/utils/e2e-setup.ts` e `test/utils/clean-database.ts` (adicione `prisma.post.deleteMany()` na limpeza) e siga o padrão de `test/users.e2e-spec.ts`.
235
+
236
+ ## Checklist rápido
237
+
238
+ - [ ] Model no `schema.prisma` + migration ou `prisma:push`
239
+ - [ ] DTOs com Zod (`createZodDto`)
240
+ - [ ] Entity (mesmo sem campo sensível ainda)
241
+ - [ ] Service sem lógica no controller
242
+ - [ ] Permissions novas cadastradas em `permissions.ts` e `role-permissions.ts`, se necessário
243
+ - [ ] Module registrado no `AppModule`
244
+ - [ ] Teste unitário do service
245
+ - [ ] Teste e2e do fluxo principal
246
+ - [ ] Atualizar `ROADMAP.md` se o módulo fechar um item do roadmap