webmoney/backend/app/Console/Commands/PopulateDemoData.php
marco 54cccdd095 refactor: migração para desenvolvimento direto no servidor
- Removido README.md padrão do Laravel (backend)
- Removidos scripts de deploy (não mais necessários)
- Atualizado copilot-instructions.md para novo fluxo
- Adicionada documentação de auditoria do servidor
- Sincronizado código de produção com repositório

Novo workflow:
- Trabalhamos diretamente em /root/webmoney (symlink para /var/www/webmoney)
- Mudanças PHP são instantâneas
- Mudanças React requerem 'npm run build'
- Commit após validação funcional
2025-12-19 11:45:32 +01:00

662 lines
26 KiB
PHP
Executable File

<?php
namespace App\Console\Commands;
use App\Models\Account;
use App\Models\Category;
use App\Models\Transaction;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class PopulateDemoData extends Command
{
protected $signature = 'demo:populate {--fresh : Limpar dados existentes antes de popular}';
protected $description = 'Popular dados de demonstração (contas, categorias, transações) para o usuário DEMO';
private User $user;
private array $accounts = [];
private array $categories = [];
private array $subcategories = [];
public function handle(): int
{
$this->user = User::where('email', 'demo@webmoney.com')->first();
if (!$this->user) {
$this->error('Usuário demo@webmoney.com não encontrado!');
return Command::FAILURE;
}
$this->info("Populando dados para usuário DEMO (ID: {$this->user->id})...");
if ($this->option('fresh')) {
$this->clearExistingData();
}
DB::beginTransaction();
try {
$this->createAccounts();
$this->createCategories();
$this->createTransactions();
// Recalcular saldos das contas
$this->recalculateBalances();
DB::commit();
$this->newLine();
$this->info('✓ Dados DEMO populados com sucesso!');
$this->showSummary();
return Command::SUCCESS;
} catch (\Exception $e) {
DB::rollBack();
$this->error('Erro: ' . $e->getMessage());
$this->error($e->getTraceAsString());
return Command::FAILURE;
}
}
private function clearExistingData(): void
{
$this->info('Limpando dados existentes...');
Transaction::where('user_id', $this->user->id)->delete();
// Subcategorias são Category com parent_id
Category::where('user_id', $this->user->id)->whereNotNull('parent_id')->delete();
Category::where('user_id', $this->user->id)->whereNull('parent_id')->delete();
Account::where('user_id', $this->user->id)->delete();
}
private function createAccounts(): void
{
$this->info('Criando contas...');
$accountsData = [
[
'name' => 'Cuenta Corriente Principal',
'type' => 'checking',
'bank_name' => 'Santander',
'account_number' => 'ES12 3456 7890 1234',
'initial_balance' => 5000.00,
'currency' => 'EUR',
'color' => '#3B82F6',
'icon' => 'bi-bank',
'is_active' => true,
'include_in_total' => true,
],
[
'name' => 'Cuenta de Ahorro',
'type' => 'savings',
'bank_name' => 'BBVA',
'account_number' => 'ES98 7654 3210 9876',
'initial_balance' => 15000.00,
'currency' => 'EUR',
'color' => '#10B981',
'icon' => 'bi-piggy-bank',
'is_active' => true,
'include_in_total' => true,
],
[
'name' => 'Efectivo',
'type' => 'cash',
'bank_name' => null,
'account_number' => null,
'initial_balance' => 500.00,
'currency' => 'EUR',
'color' => '#F59E0B',
'icon' => 'bi-cash-stack',
'is_active' => true,
'include_in_total' => true,
],
];
foreach ($accountsData as $data) {
$account = Account::create([
'user_id' => $this->user->id,
'name' => $data['name'],
'type' => $data['type'],
'bank_name' => $data['bank_name'],
'account_number' => $data['account_number'],
'initial_balance' => $data['initial_balance'],
'current_balance' => $data['initial_balance'],
'currency' => $data['currency'],
'color' => $data['color'],
'icon' => $data['icon'],
'is_active' => $data['is_active'],
'include_in_total' => $data['include_in_total'],
]);
$this->accounts[$data['type']] = $account;
$this->info("{$data['name']}");
}
}
private function createCategories(): void
{
$this->info('Criando categorias e subcategorias...');
$categoriesData = [
// DESPESAS (expense)
[
'name' => 'Vivienda',
'type' => 'debit',
'icon' => 'bi-house',
'color' => '#EF4444',
'subcategories' => ['Alquiler', 'Hipoteca', 'Comunidad', 'Seguro Hogar', 'Reparaciones', 'Muebles'],
],
[
'name' => 'Alimentación',
'type' => 'debit',
'icon' => 'bi-cart',
'color' => '#F97316',
'subcategories' => ['Supermercado', 'Restaurantes', 'Cafeterías', 'Delivery', 'Panadería'],
],
[
'name' => 'Transporte',
'type' => 'debit',
'icon' => 'bi-car-front',
'color' => '#8B5CF6',
'subcategories' => ['Combustible', 'Transporte Público', 'Taxi/Uber', 'Mantenimiento Coche', 'Parking', 'Seguro Coche'],
],
[
'name' => 'Servicios',
'type' => 'debit',
'icon' => 'bi-lightning',
'color' => '#EC4899',
'subcategories' => ['Electricidad', 'Gas', 'Agua', 'Internet', 'Teléfono', 'Streaming'],
],
[
'name' => 'Salud',
'type' => 'debit',
'icon' => 'bi-heart-pulse',
'color' => '#14B8A6',
'subcategories' => ['Médico', 'Farmacia', 'Dentista', 'Óptica', 'Gimnasio', 'Seguro Médico'],
],
[
'name' => 'Ocio',
'type' => 'debit',
'icon' => 'bi-controller',
'color' => '#6366F1',
'subcategories' => ['Cine', 'Conciertos', 'Viajes', 'Hobbies', 'Libros', 'Videojuegos'],
],
[
'name' => 'Ropa',
'type' => 'debit',
'icon' => 'bi-bag',
'color' => '#A855F7',
'subcategories' => ['Ropa', 'Calzado', 'Accesorios', 'Ropa Deportiva'],
],
[
'name' => 'Educación',
'type' => 'debit',
'icon' => 'bi-book',
'color' => '#0EA5E9',
'subcategories' => ['Cursos', 'Material Escolar', 'Idiomas', 'Certificaciones'],
],
[
'name' => 'Mascotas',
'type' => 'debit',
'icon' => 'bi-piggy-bank',
'color' => '#84CC16',
'subcategories' => ['Comida Mascota', 'Veterinario', 'Accesorios Mascota'],
],
[
'name' => 'Otros Gastos',
'type' => 'debit',
'icon' => 'bi-three-dots',
'color' => '#64748B',
'subcategories' => ['Regalos', 'Donaciones', 'Imprevistos', 'Varios'],
],
// INGRESOS (income)
[
'name' => 'Salario',
'type' => 'credit',
'icon' => 'bi-briefcase',
'color' => '#22C55E',
'subcategories' => ['Nómina', 'Horas Extra', 'Bonus', 'Comisiones'],
],
[
'name' => 'Inversiones',
'type' => 'credit',
'icon' => 'bi-graph-up-arrow',
'color' => '#10B981',
'subcategories' => ['Dividendos', 'Intereses', 'Plusvalías', 'Alquileres'],
],
[
'name' => 'Freelance',
'type' => 'credit',
'icon' => 'bi-laptop',
'color' => '#06B6D4',
'subcategories' => ['Proyectos', 'Consultoría', 'Clases Particulares'],
],
[
'name' => 'Otros Ingresos',
'type' => 'credit',
'icon' => 'bi-plus-circle',
'color' => '#84CC16',
'subcategories' => ['Reembolsos', 'Ventas', 'Premios', 'Herencias'],
],
];
foreach ($categoriesData as $catData) {
$category = Category::create([
'user_id' => $this->user->id,
'name' => $catData['name'],
'type' => $catData['type'],
'icon' => $catData['icon'],
'color' => $catData['color'],
'is_active' => true,
'parent_id' => null,
]);
$this->categories[$catData['name']] = $category;
// Subcategorias são Category com parent_id
foreach ($catData['subcategories'] as $subName) {
$sub = Category::create([
'user_id' => $this->user->id,
'parent_id' => $category->id,
'name' => $subName,
'type' => $catData['type'],
'icon' => $catData['icon'],
'color' => $catData['color'],
'is_active' => true,
]);
$this->subcategories[$subName] = $sub;
}
$this->info("{$catData['name']} ({$catData['type']}) - " . count($catData['subcategories']) . " subcategorias");
}
}
private function createTransactions(): void
{
$this->info('Criando transações de 2025-2026...');
$checkingAccount = $this->accounts['checking'];
$savingsAccount = $this->accounts['savings'];
$cashAccount = $this->accounts['cash'];
$transactionCount = 0;
$today = Carbon::today();
// Helper para determinar status baseado na data
$getStatus = function(Carbon $date) use ($today) {
return $date->isAfter($today) ? 'pending' : 'effective';
};
// Gerar transações de Janeiro 2025 a Março 2026
for ($i = 1; $i <= 15; $i++) { // 12 meses de 2025 + 3 de 2026
$year = $i <= 12 ? 2025 : 2026;
$month = $i <= 12 ? $i : $i - 12;
$daysInMonth = Carbon::create($year, $month)->daysInMonth;
// RECEITAS FIXAS (mensais)
// Salário - dia 28 ou último dia útil
$salaryDay = min(28, $daysInMonth);
$salaryDate = Carbon::create($year, $month, $salaryDay);
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Salario',
'subcategory' => 'Nómina',
'type' => 'credit',
'amount' => 3200.00,
'date' => $salaryDate,
'status' => $getStatus($salaryDate),
'description' => 'Salario mensual',
]);
$transactionCount++;
// DESPESAS FIXAS (mensais)
// Aluguel - dia 1
$rentDate = Carbon::create($year, $month, 1);
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Vivienda',
'subcategory' => 'Alquiler',
'type' => 'debit',
'amount' => 850.00,
'date' => $rentDate,
'status' => $getStatus($rentDate),
'description' => 'Alquiler apartamento',
]);
$transactionCount++;
// Serviços - vários dias do mês
$services = [
['subcategory' => 'Electricidad', 'amount' => rand(45, 85), 'day' => 5],
['subcategory' => 'Gas', 'amount' => rand(25, 55), 'day' => 8],
['subcategory' => 'Agua', 'amount' => rand(20, 35), 'day' => 10],
['subcategory' => 'Internet', 'amount' => 49.99, 'day' => 15],
['subcategory' => 'Teléfono', 'amount' => 25.00, 'day' => 15],
['subcategory' => 'Streaming', 'amount' => 17.99, 'day' => 20],
];
foreach ($services as $service) {
if ($service['day'] <= $daysInMonth) {
$serviceDate = Carbon::create($year, $month, $service['day']);
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Servicios',
'subcategory' => $service['subcategory'],
'type' => 'debit',
'amount' => $service['amount'],
'date' => $serviceDate,
'status' => $getStatus($serviceDate),
'description' => $service['subcategory'],
]);
$transactionCount++;
}
}
// Supermercado - várias vezes por mês
$supermarketDays = [3, 10, 17, 24];
foreach ($supermarketDays as $day) {
if ($day <= $daysInMonth) {
$marketDate = Carbon::create($year, $month, $day);
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Alimentación',
'subcategory' => 'Supermercado',
'type' => 'debit',
'amount' => rand(60, 120),
'date' => $marketDate,
'status' => $getStatus($marketDate),
'description' => 'Compra supermercado',
]);
$transactionCount++;
}
}
// Transporte - combustível e outros
$fuelDate = Carbon::create($year, $month, rand(1, min(15, $daysInMonth)));
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Transporte',
'subcategory' => 'Combustible',
'type' => 'debit',
'amount' => rand(50, 80),
'date' => $fuelDate,
'status' => $getStatus($fuelDate),
'description' => 'Gasolina',
]);
$transactionCount++;
// Restaurantes - algumas vezes por mês
$restaurantCount = rand(2, 4);
for ($j = 0; $j < $restaurantCount; $j++) {
$restaurantDate = Carbon::create($year, $month, rand(1, $daysInMonth));
$this->createTransaction([
'account_id' => $cashAccount->id,
'category' => 'Alimentación',
'subcategory' => 'Restaurantes',
'type' => 'debit',
'amount' => rand(25, 60),
'date' => $restaurantDate,
'status' => $getStatus($restaurantDate),
'description' => 'Cena/Almuerzo fuera',
]);
$transactionCount++;
}
// Café - várias vezes
$coffeeCount = rand(5, 10);
for ($j = 0; $j < $coffeeCount; $j++) {
$coffeeDate = Carbon::create($year, $month, rand(1, $daysInMonth));
$this->createTransaction([
'account_id' => $cashAccount->id,
'category' => 'Alimentación',
'subcategory' => 'Cafeterías',
'type' => 'debit',
'amount' => rand(3, 8),
'date' => $coffeeDate,
'status' => $getStatus($coffeeDate),
'description' => 'Café',
]);
$transactionCount++;
}
// Retirada de cajero para efectivo (do banco para cash)
$atmDate = Carbon::create($year, $month, rand(1, min(5, $daysInMonth)));
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Otros Gastos',
'subcategory' => 'Varios',
'type' => 'debit',
'amount' => 200.00,
'date' => $atmDate,
'status' => $getStatus($atmDate),
'description' => 'Retiro cajero automático',
]);
$transactionCount++;
$this->createTransaction([
'account_id' => $cashAccount->id,
'category' => 'Otros Ingresos',
'subcategory' => 'Reembolsos',
'type' => 'credit',
'amount' => 200.00,
'date' => $atmDate,
'status' => $getStatus($atmDate),
'description' => 'Retiro cajero automático',
]);
$transactionCount++;
// Saúde - eventual
if (rand(1, 3) == 1) {
$healthDate = Carbon::create($year, $month, rand(1, $daysInMonth));
$healthSubs = ['Farmacia', 'Médico', 'Gimnasio'];
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Salud',
'subcategory' => $healthSubs[array_rand($healthSubs)],
'type' => 'debit',
'amount' => rand(15, 80),
'date' => $healthDate,
'status' => $getStatus($healthDate),
'description' => 'Gasto salud',
]);
$transactionCount++;
}
// Ginásio - mensal
$gymDate = Carbon::create($year, $month, 1);
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Salud',
'subcategory' => 'Gimnasio',
'type' => 'debit',
'amount' => 35.00,
'date' => $gymDate,
'status' => $getStatus($gymDate),
'description' => 'Cuota gimnasio',
]);
$transactionCount++;
// Lazer - algumas vezes
if (rand(1, 2) == 1) {
$leisureDate = Carbon::create($year, $month, rand(1, $daysInMonth));
$leisureSubs = ['Cine', 'Conciertos', 'Hobbies', 'Libros', 'Videojuegos'];
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Ocio',
'subcategory' => $leisureSubs[array_rand($leisureSubs)],
'type' => 'debit',
'amount' => rand(15, 60),
'date' => $leisureDate,
'status' => $getStatus($leisureDate),
'description' => 'Entretenimiento',
]);
$transactionCount++;
}
// Roupa - eventual
if (rand(1, 4) == 1) {
$clothesDate = Carbon::create($year, $month, rand(1, $daysInMonth));
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Ropa',
'subcategory' => 'Ropa',
'type' => 'debit',
'amount' => rand(30, 120),
'date' => $clothesDate,
'status' => $getStatus($clothesDate),
'description' => 'Compra ropa',
]);
$transactionCount++;
}
// Freelance - eventual (2-3 vezes por trimestre)
if ($month % 3 == 0 || rand(1, 5) == 1) {
$freelanceDate = Carbon::create($year, $month, rand(10, min(25, $daysInMonth)));
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Freelance',
'subcategory' => 'Proyectos',
'type' => 'credit',
'amount' => rand(200, 800),
'date' => $freelanceDate,
'status' => $getStatus($freelanceDate),
'description' => 'Proyecto freelance',
]);
$transactionCount++;
}
// Dividendos - trimestral
if ($month % 3 == 0) {
$dividendDate = Carbon::create($year, $month, 15);
$this->createTransaction([
'account_id' => $savingsAccount->id,
'category' => 'Inversiones',
'subcategory' => 'Dividendos',
'type' => 'credit',
'amount' => rand(50, 150),
'date' => $dividendDate,
'status' => $getStatus($dividendDate),
'description' => 'Dividendos trimestre',
]);
$transactionCount++;
}
// Juros poupança - mensal
$interestDate = Carbon::create($year, $month, $daysInMonth);
$this->createTransaction([
'account_id' => $savingsAccount->id,
'category' => 'Inversiones',
'subcategory' => 'Intereses',
'type' => 'credit',
'amount' => round(rand(15, 35) + (rand(0, 99) / 100), 2),
'date' => $interestDate,
'status' => $getStatus($interestDate),
'description' => 'Intereses cuenta ahorro',
]);
$transactionCount++;
// Transferência para poupança - mensal
if (rand(1, 2) == 1) {
$transferDate = Carbon::create($year, $month, rand(25, min(28, $daysInMonth)));
$transferAmount = rand(200, 500);
$this->createTransaction([
'account_id' => $checkingAccount->id,
'category' => 'Otros Gastos',
'subcategory' => 'Varios',
'type' => 'debit',
'amount' => $transferAmount,
'date' => $transferDate,
'status' => $getStatus($transferDate),
'description' => 'Transferencia a cuenta ahorro',
]);
$transactionCount++;
$this->createTransaction([
'account_id' => $savingsAccount->id,
'category' => 'Otros Ingresos',
'subcategory' => 'Reembolsos',
'type' => 'credit',
'amount' => $transferAmount,
'date' => $transferDate,
'status' => $getStatus($transferDate),
'description' => 'Transferencia desde cuenta corriente',
]);
$transactionCount++;
}
$this->info(" ✓ Mes $month/$year procesado");
}
$this->info(" Total: $transactionCount transacciones creadas");
}
private function createTransaction(array $data): Transaction
{
$category = $this->categories[$data['category']] ?? null;
// Subcategoria é usada diretamente como category_id (pois são Categories com parent_id)
$subcategory = $this->subcategories[$data['subcategory']] ?? null;
// Se tiver subcategoria, usa ela; senão usa a categoria pai
$categoryId = $subcategory?->id ?? $category?->id;
// Status vem do data ou default 'effective'
$status = $data['status'] ?? 'effective';
// Para transações pendentes, não definir effective_date
$effectiveDate = $status === 'pending' ? null : $data['date'];
return Transaction::create([
'user_id' => $this->user->id,
'account_id' => $data['account_id'],
'category_id' => $categoryId,
'type' => $data['type'],
'amount' => $status === 'pending' ? null : $data['amount'],
'planned_amount' => $data['amount'],
'planned_date' => $data['date'],
'effective_date' => $effectiveDate,
'description' => $data['description'],
'notes' => null,
'is_recurring' => false,
'status' => $status,
]);
}
private function recalculateBalances(): void
{
$this->info('Recalculando saldos das contas...');
foreach ($this->accounts as $account) {
// Apenas transações efetivas afetam o saldo atual
$income = Transaction::where('account_id', $account->id)
->where('type', 'credit')
->where('status', 'effective')
->sum('amount');
$expense = Transaction::where('account_id', $account->id)
->where('type', 'debit')
->where('status', 'effective')
->sum('amount');
$newBalance = $account->initial_balance + $income - $expense;
$account->update(['current_balance' => $newBalance]);
$this->info("{$account->name}: €" . number_format($newBalance, 2));
}
}
private function showSummary(): void
{
$this->newLine();
$this->info('=== RESUMO ===');
$this->info('Contas: ' . count($this->accounts));
$this->info('Categorias: ' . count($this->categories));
$this->info('Subcategorias: ' . count($this->subcategories));
$this->info('Transações: ' . Transaction::where('user_id', $this->user->id)->count());
$this->newLine();
$this->info('Saldos finais:');
foreach ($this->accounts as $account) {
$account->refresh();
$this->info(" {$account->name}: €" . number_format($account->current_balance, 2));
}
}
}