Skip to content

Commit a0afb22

Browse files
committed
feat: Add LGPD helpers for data normalization and enhance financial formatting across various components, improving data privacy and user experience.
1 parent 7cb743f commit a0afb22

103 files changed

Lines changed: 2083 additions & 164 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

GloablsSystem.md

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,75 @@ Basta adicionar a diretiva `x-mask` com o nome da máscara desejada. O sistema c
240240
| `'date'` | DD/MM/AAAA | 25/12/2025 |
241241

242242
### Melhores Práticas (Backend)
243-
Lembre-se que as máscaras enviam os caracteres especiais (pontos, traços). Sempre **limpe os dados** no backend antes de salvar no banco ou validar.
243+
Lembre-se que as máscaras enviam os caracteres especiais (pontos, traços). Use os helpers `lgpd_clean_*` para normalizar dados antes de salvar no banco ou validar.
244244

245245
**Exemplo no Laravel (Request ou Controller):**
246246
```php
247-
// Remover tudo que não for dígito
248-
$cpfClean = preg_replace('/\D/', '', $request->cpf);
249-
// Salvar: 12345678900
247+
$request->merge([
248+
'cpf' => lgpd_clean_cpf($request->cpf ?? null) ?: null,
249+
'phone' => lgpd_clean_phone($request->phone ?? null) ?: null,
250+
]);
251+
// Salvar: 12345678900 (apenas dígitos)
252+
```
253+
254+
---
255+
256+
## 6. Helpers LGPD e Formatação
257+
258+
O sistema possui helpers globais para proteção LGPD e formatação consistente de valores financeiros e dados sensíveis.
259+
260+
### 6.1 LgpdHelper (Segurança LGPD)
261+
262+
Protege dados pessoais conforme LGPD. Use em exibição, logs e contextos de suporte.
263+
264+
| Função | Uso |
265+
|--------|-----|
266+
| `lgpd_mask_cpf($cpf)` | Exibe `***.***.***-00` em contextos de terceiros |
267+
| `lgpd_format_cpf($cpf)` | Formata `123.456.789-00` para perfil próprio |
268+
| `lgpd_mask_cnpj($cnpj)` | Mascara CNPJ em documentos de terceiros |
269+
| `lgpd_format_cnpj($cnpj)` | Formata CNPJ para exibição legível |
270+
| `lgpd_mask_phone($phone)` | Exibe `(11) *****-7777` |
271+
| `lgpd_format_phone($phone)` | Formata `(11) 98888-7777` |
272+
| `lgpd_clean_cpf($value)` | Remove não-dígitos (antes de salvar) |
273+
| `lgpd_clean_cnpj($value)` | Remove não-dígitos |
274+
| `lgpd_clean_phone($value)` | Remove não-dígitos |
275+
276+
**Política:** Em suporte/inspeção visualizando usuário terceiro → **sempre mask**. Em perfil próprio ou documento da empresa → **format**.
277+
278+
### 6.2 FormatHelper (Moeda, Porcentagem)
279+
280+
| Função | Uso |
281+
|--------|-----|
282+
| `format_currency($value)` | `R$ 1.500,50` (respeita InspectionGuard) |
283+
| `format_percent($value, $decimals)` | `15,5%` |
284+
| `format_number($value, $decimals)` | `1.500,50` (padrão BRL) |
285+
286+
### 6.3 Máscaras de Input (x-mask) - Autoformatação na Digitação
287+
288+
Campos com `x-mask` formatam automaticamente enquanto o usuário digita:
289+
290+
| Campo | Máscara | Onde está aplicado |
291+
|-----------|-----------|------------------------------------------------------------------|
292+
| CPF | `x-mask="'cpf'"` | Login, Cadastro, Perfil Suporte (edição) |
293+
| CNPJ | `x-mask="'cnpj'"` | Configurações Admin (documentos) |
294+
| Telefone | `x-mask="'phone'"`| Cadastro, Perfis (PanelUser, Admin, Suporte), Usuários Suporte |
295+
| Data | `x-mask="'date'"` | Cadastro, Perfis, Usuários Suporte (DD/MM/AAAA) |
296+
| Moeda | `x-mask="'money'"`| Minha Renda, Onboarding, Transações (formatCurrency) |
297+
298+
**Componente reutilizável:** `<x-masked-input mask="cpf" name="cpf" />` (em `resources/views/components/masked-input.blade.php`)
299+
300+
### 6.4 Helpers de parsing (backend)
301+
302+
```php
303+
parse_brl_money($request->amount); // "R$ 1.500,50" -> 1500.50
304+
parse_brl_date($request->birth_date); // "25/12/2025" -> "2025-12-25"
305+
```
306+
307+
### 6.5 Exemplos de exibição
308+
309+
```blade
310+
{{ format_currency($invoice->amount) }}
311+
{{ format_percent($budget->usage_percentage, 1) }}
312+
{{ lgpd_mask_cpf($ticket->user->cpf) }}
313+
{{ lgpd_format_phone($user->phone) }}
250314
```
251-
Isso garante integridade dos dados e facilita buscas futuras.

Modules/Core/app/Console/Commands/ProcessRecurringTransactions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function handle(): int
5454
$this->line(sprintf(
5555
'Processed: %s - R$ %s for user %s',
5656
$recurring->description,
57-
number_format($recurring->amount, 2, ',', '.'),
57+
format_number($recurring->amount, 2),
5858
$recurring->user->name
5959
));
6060

Modules/Core/app/Http/Controllers/InvoiceController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function index()
6262
if ($activeSubscription && $activeSubscription->current_period_end) {
6363
$nextDue = $activeSubscription->current_period_end;
6464
$nextDueLabel = $nextDue->format('d/m/Y');
65-
$amount = number_format((float) $activeSubscription->amount, 2, ',', '.');
65+
$amount = format_number((float) $activeSubscription->amount, 2);
6666
$nextDueSubtext = "Próxima cobrança de R$ {$amount} será debitada nesta data. Assinatura mensal recorrente.";
6767
} else {
6868
// Fallback: último pagamento + 1 mês
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Modules\Core\Models;
6+
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
class LegalDocument extends Model
10+
{
11+
protected $fillable = [
12+
'slug',
13+
'title',
14+
'content',
15+
'version',
16+
'is_active',
17+
'requires_acceptance',
18+
];
19+
20+
protected $casts = [
21+
'is_active' => 'boolean',
22+
'requires_acceptance' => 'boolean',
23+
];
24+
25+
public function acceptances()
26+
{
27+
return $this->hasMany(UserLegalAcceptance::class);
28+
}
29+
30+
public function scopeActive($query)
31+
{
32+
return $query->where('is_active', true);
33+
}
34+
35+
public function scopeRequiresAcceptance($query)
36+
{
37+
return $query->where('requires_acceptance', true);
38+
}
39+
40+
public static function getBySlug(string $slug): ?self
41+
{
42+
return self::where('slug', $slug)->first();
43+
}
44+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Modules\Core\Models;
6+
7+
use App\Models\User;
8+
use Illuminate\Database\Eloquent\Model;
9+
10+
class UserLegalAcceptance extends Model
11+
{
12+
protected $fillable = [
13+
'user_id',
14+
'legal_document_id',
15+
'version',
16+
'accepted_at',
17+
'ip_address',
18+
];
19+
20+
protected $casts = [
21+
'accepted_at' => 'datetime',
22+
];
23+
24+
public function user()
25+
{
26+
return $this->belongsTo(User::class);
27+
}
28+
29+
public function legalDocument()
30+
{
31+
return $this->belongsTo(LegalDocument::class);
32+
}
33+
}

Modules/Core/app/Services/ReportService.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ public function exportCashFlowToCsv(Collection $cashFlow, string $filename, stri
377377
$totalIncome = $cashFlow->sum('income');
378378
$totalExpense = $cashFlow->sum('expense');
379379
$totalBalance = $cashFlow->sum('balance');
380-
$totalsRow = ['TOTAL', 'R$ ' . number_format($totalIncome, 2, ',', '.'), 'R$ ' . number_format($totalExpense, 2, ',', '.'), 'R$ ' . number_format($totalBalance, 2, ',', '.')];
380+
$totalsRow = ['TOTAL', format_currency($totalIncome, 'R$', false), format_currency($totalExpense, 'R$', false), format_currency($totalBalance, 'R$', false)];
381381
}
382382

383383
return $this->exportToCsv($cashFlow, $filename, $headerBlock, $totalsRow);
@@ -561,13 +561,13 @@ public function exportCategoryRankingToCsv(Collection $ranking, string $filename
561561
$rows = $ranking->map(fn ($item) => [
562562
'Categoria' => $item['category'],
563563
'Transações' => $item['count'],
564-
'Total' => 'R$ ' . number_format($item['total'], 2, ',', '.'),
564+
'Total' => format_currency($item['total'], 'R$', false),
565565
]);
566566

567567
$totalsRow = null;
568568
if ($ranking->isNotEmpty()) {
569569
$total = $ranking->sum('total');
570-
$totalsRow = ['TOTAL', $ranking->sum('count'), 'R$ ' . number_format($total, 2, ',', '.')];
570+
$totalsRow = ['TOTAL', $ranking->sum('count'), format_currency($total, 'R$', false)];
571571
}
572572

573573
return $this->exportToCsv($rows, $filename, $headerBlock, $totalsRow);
@@ -591,14 +591,14 @@ public function exportBankStatementToCsv(Collection $statement, string $filename
591591
'Descrição' => $item['transaction']->description ?? '',
592592
'Categoria' => $item['transaction']->category?->name ?? '',
593593
'Conta' => $item['transaction']->account?->name ?? '',
594-
'Crédito' => 'R$ ' . number_format($item['credit'], 2, ',', '.'),
595-
'Débito' => 'R$ ' . number_format($item['debit'], 2, ',', '.'),
596-
'Saldo' => 'R$ ' . number_format($item['balance'], 2, ',', '.'),
594+
'Crédito' => format_currency($item['credit'], 'R$', false),
595+
'Débito' => format_currency($item['debit'], 'R$', false),
596+
'Saldo' => format_currency($item['balance'], 'R$', false),
597597
];
598598
});
599599

600600
$totals = $this->getBankStatementTotals($statement);
601-
$totalsRow = ['TOTAL', '', '', '', 'R$ ' . number_format($totals['total_credit'], 2, ',', '.'), 'R$ ' . number_format($totals['total_debit'], 2, ',', '.'), 'R$ ' . number_format($totals['final_balance'], 2, ',', '.')];
601+
$totalsRow = ['TOTAL', '', '', '', format_currency($totals['total_credit'], 'R$', false), format_currency($totals['total_debit'], 'R$', false), format_currency($totals['final_balance'], 'R$', false)];
602602

603603
return $this->exportToCsv($rows, $filename, $headerBlock, $totalsRow);
604604
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('legal_documents', function (Blueprint $table) {
15+
$table->id();
16+
$table->string('slug')->unique();
17+
$table->string('title');
18+
$table->longText('content');
19+
$table->string('version');
20+
$table->boolean('is_active')->default(true);
21+
$table->boolean('requires_acceptance')->default(false);
22+
$table->timestamps();
23+
$table->index('is_active');
24+
});
25+
}
26+
27+
/**
28+
* Reverse the migrations.
29+
*/
30+
public function down(): void
31+
{
32+
Schema::dropIfExists('legal_documents');
33+
}
34+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('user_legal_acceptances', function (Blueprint $table) {
15+
$table->id();
16+
$table->foreignId('user_id')->constrained()->onDelete('cascade');
17+
$table->foreignId('legal_document_id')->constrained('legal_documents')->onDelete('cascade');
18+
$table->string('version');
19+
$table->timestamp('accepted_at');
20+
$table->string('ip_address', 45)->nullable();
21+
$table->timestamps();
22+
});
23+
24+
Schema::table('user_legal_acceptances', function (Blueprint $table) {
25+
$table->unique(['user_id', 'legal_document_id']);
26+
});
27+
}
28+
29+
/**
30+
* Reverse the migrations.
31+
*/
32+
public function down(): void
33+
{
34+
Schema::dropIfExists('user_legal_acceptances');
35+
}
36+
};

Modules/Core/database/seeders/CoreDatabaseSeeder.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ class CoreDatabaseSeeder extends Seeder
1717
*/
1818
public function run(): void
1919
{
20-
$this->call([SettingSeeder::class]);
20+
$this->call([
21+
SettingSeeder::class,
22+
LegalDocumentSeeder::class,
23+
]);
2124
}
2225
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Modules\Core\Database\Seeders;
6+
7+
use Illuminate\Database\Seeder;
8+
use Modules\Core\Models\LegalDocument;
9+
10+
class LegalDocumentSeeder extends Seeder
11+
{
12+
/**
13+
* Run the database seeds.
14+
*/
15+
public function run(): void
16+
{
17+
$documents = [
18+
[
19+
'slug' => 'termos-de-uso',
20+
'title' => 'Termos de Uso',
21+
'content' => '<h1>Termos de Uso - Vertex Contas</h1><p>Estes Termos de Uso regem o uso do serviço Vertex Contas, operado pela <strong>Vertex Solutions LTDA</strong>, pessoa jurídica de direito privado, inscrita sob CNPJ, com sede no Brasil.</p><p>Ao utilizar nossos serviços, o usuário declara ter lido, compreendido e concordado integralmente com estes termos, em conformidade com a legislação brasileira vigente.</p>',
22+
'version' => '1.0.0',
23+
'is_active' => true,
24+
'requires_acceptance' => true,
25+
],
26+
[
27+
'slug' => 'privacidade',
28+
'title' => 'Política de Privacidade (LGPD)',
29+
'content' => '<h1>Política de Privacidade - LGPD</h1><p>A <strong>Vertex Solutions LTDA</strong> está comprometida com a proteção dos dados pessoais de seus usuários, em conformidade com a Lei Geral de Proteção de Dados (Lei nº 13.709/2018).</p><p>Esta política descreve como coletamos, armazenamos, utilizamos e protegemos suas informações pessoais, garantindo transparência e o exercício dos direitos previstos na LGPD.</p>',
30+
'version' => '1.0.0',
31+
'is_active' => true,
32+
'requires_acceptance' => true,
33+
],
34+
[
35+
'slug' => 'termos-assinatura',
36+
'title' => 'Termos de Assinatura',
37+
'content' => '<h1>Termos de Assinatura - Vertex Contas</h1><p>Os presentes Termos de Assinatura celebrados entre o usuário e a <strong>Vertex Solutions LTDA</strong> estabelecem as condições da prestação do serviço de controle financeiro mediante assinatura.</p><p>O contrato é celebrado em conformidade com o Código de Defesa do Consumidor e demais normas aplicáveis no território nacional.</p>',
38+
'version' => '1.0.0',
39+
'is_active' => true,
40+
'requires_acceptance' => false,
41+
],
42+
[
43+
'slug' => 'politica-cookies',
44+
'title' => 'Política de Cookies',
45+
'content' => '<h1>Política de Cookies - Vertex Contas</h1><p>A <strong>Vertex Solutions LTDA</strong> utiliza cookies e tecnologias similares para melhorar a experiência do usuário em nossa plataforma Vertex Contas.</p><p>Esta política descreve os tipos de cookies utilizados, suas finalidades e como o usuário pode gerenciar suas preferências, em conformidade com a LGPD e as melhores práticas de privacidade.</p>',
46+
'version' => '1.0.0',
47+
'is_active' => true,
48+
'requires_acceptance' => false,
49+
],
50+
];
51+
52+
foreach ($documents as $document) {
53+
LegalDocument::updateOrCreate(
54+
['slug' => $document['slug']],
55+
$document
56+
);
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)