From e8aac6eaa556d858bac9bf62ddfe668971ca036f Mon Sep 17 00:00:00 2001 From: nickpons666 Date: Tue, 21 Apr 2026 13:45:39 -0600 Subject: [PATCH] =?UTF-8?q?Feat:=20Implementado=20c=C3=A1lculo=20de=20ISR?= =?UTF-8?q?=20en=20n=C3=B3minas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Agregado campo isr_table_id en tabla months para seleccionar tabla ISR por mes - Creado servicio IsrCalculator para calcular ISR mensual y quincenal - Modificado CommissionCalculator para descontar ISR del total a pagar - Agregado selector de tabla ISR en formulario de crear/editar mes - Actualizada vista de meses para mostrar tabla ISR asignada - Actualizados reportes mensual y quincenal para mostrar ISR descontado --- app/Http/Controllers/MonthController.php | 11 +++- app/Models/Month.php | 11 +++- app/Services/CommissionCalculator.php | 43 ++++++++---- app/Services/IsrCalculator.php | 65 +++++++++++++++++++ ...00002_add_isr_table_id_to_months_table.php | 29 +++++++++ resources/views/months/create.blade.php | 13 ++++ resources/views/months/edit.blade.php | 13 ++++ resources/views/months/index.blade.php | 9 +++ resources/views/reports/biweekly.blade.php | 34 ++++++++-- resources/views/reports/monthly.blade.php | 6 ++ 10 files changed, 213 insertions(+), 21 deletions(-) create mode 100644 app/Services/IsrCalculator.php create mode 100644 database/migrations/2026_04_21_000002_add_isr_table_id_to_months_table.php diff --git a/app/Http/Controllers/MonthController.php b/app/Http/Controllers/MonthController.php index 128f465..3c94c5d 100755 --- a/app/Http/Controllers/MonthController.php +++ b/app/Http/Controllers/MonthController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Models\IsrTable; use App\Models\Month; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -16,6 +17,7 @@ class MonthController extends Controller $user = Auth::user(); $months = $user->months() + ->with('isrTable') ->orderBy('year', 'desc') ->orderByRaw("FIELD(name, 'Diciembre', 'Noviembre', 'Octubre', 'Septiembre', 'Agosto', 'Julio', 'Junio', 'Mayo', 'Abril', 'Marzo', 'Febrero', 'Enero')") ->paginate(12); @@ -28,7 +30,8 @@ class MonthController extends Controller */ public function create() { - return view('months.create'); + $isrTables = IsrTable::with('brackets')->orderBy('year', 'desc')->get(); + return view('months.create', compact('isrTables')); } /** @@ -41,6 +44,7 @@ class MonthController extends Controller $validated = $request->validate([ 'name' => ['required', 'string', 'max:50'], 'year' => ['required', 'integer', 'min:2020', 'max:2100'], + 'isr_table_id' => ['nullable', 'exists:isr_tables,id'], ]); // Verificar que no exista ya el mes para el usuario @@ -59,6 +63,7 @@ class MonthController extends Controller 'name' => $validated['name'], 'year' => $validated['year'], 'status' => 'open', + 'isr_table_id' => $validated['isr_table_id'] ?? null, ]); return redirect()->route('months.index') @@ -92,7 +97,8 @@ class MonthController extends Controller abort(403); } - return view('months.edit', compact('month')); + $isrTables = IsrTable::with('brackets')->orderBy('year', 'desc')->get(); + return view('months.edit', compact('month', 'isrTables')); } /** @@ -110,6 +116,7 @@ class MonthController extends Controller 'name' => ['required', 'string', 'max:50'], 'year' => ['required', 'integer', 'min:2020', 'max:2100'], 'status' => ['required', 'in:open,closed,paid'], + 'isr_table_id' => ['nullable', 'exists:isr_tables,id'], ]); $month->update($validated); diff --git a/app/Models/Month.php b/app/Models/Month.php index 9096831..6cc1347 100755 --- a/app/Models/Month.php +++ b/app/Models/Month.php @@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -#[Fillable(['user_id', 'name', 'year', 'status'])] +#[Fillable(['user_id', 'name', 'year', 'status', 'isr_table_id'])] class Month extends Model { use HasFactory; @@ -25,6 +25,7 @@ class Month extends Model 'name', 'year', 'status', + 'isr_table_id', ]; /** @@ -63,6 +64,14 @@ class Month extends Model return $this->hasMany(Expense::class); } + /** + * Relación con tabla ISR + */ + public function isrTable(): BelongsTo + { + return $this->belongsTo(IsrTable::class); + } + /** * Obtener el nombre del mes con formato */ diff --git a/app/Services/CommissionCalculator.php b/app/Services/CommissionCalculator.php index 861fd3f..8ff548a 100755 --- a/app/Services/CommissionCalculator.php +++ b/app/Services/CommissionCalculator.php @@ -4,6 +4,7 @@ namespace App\Services; use App\Models\DailySale; use App\Models\Expense; +use App\Models\IsrTable; use App\Models\Month; use App\Models\User; use Illuminate\Support\Facades\DB; @@ -38,8 +39,15 @@ class CommissionCalculator // Calcular comisión basada en ventas del sistema (ventas consolidadas) $commission = ($totalSystemSales * $commissionPercentage) / 100; - // Calcular percepción total (salario + comisión - gastos) - $totalEarning = $monthlySalary + $commission - $totalExpenses; + // Calcular ISR sobre (salario + comisión) + // Los gastos personales NO son deducibles del ISR + $incomeForIsr = $monthlySalary + $commission; + $isrTable = $month->isrTable; + $isrResult = IsrCalculator::calculateMonthly($incomeForIsr, $isrTable); + $isrAmount = $isrResult['isr']; + + // Calcular percepción total con ISR (salario + comisión - gastos - ISR) + $totalEarning = $monthlySalary + $commission - $totalExpenses - $isrAmount; return [ 'user_id' => $user->id, @@ -51,6 +59,8 @@ class CommissionCalculator 'monthly_salary' => round($monthlySalary, 2), 'commission_percentage' => round($commissionPercentage, 2), 'commission_amount' => round($commission, 2), + 'isr_amount' => round($isrAmount, 2), + 'isr_details' => $isrResult, 'total_earning' => round($totalEarning, 2), 'has_difference' => ($totalUserSales !== $totalSystemSales), 'sales_difference' => round($totalUserSales - $totalSystemSales, 2), @@ -60,8 +70,8 @@ class CommissionCalculator /** * Calcular quincena (primera o segunda) * - * QUINCENA 1 (ANTICIPO): mitad salary + comisiones del MES completo - * QUINCENA 2 (LIQUIDACIÓN): mitad salary - gastos de la segunda quincena + * QUINCENA 1 (ANTICIPO): mitad salary + comisiones del MES completo - ISR quincenal + * QUINCENA 2 (LIQUIDACIÓN): mitad salary - gastos de la segunda quincena - ISR quincenal */ public static function calculateBiweekly(User $user, Month $month, int $biweekly): array { @@ -78,13 +88,20 @@ class CommissionCalculator $year = $month->year; $lastDay = self::getLastDayOfMonth($month->name, $year); + // Calcular ISR mensual completo (salario + comisión) + $totalSystemSales = $month->dailySales()->sum('system_sales'); + $commission = ($totalSystemSales * $commissionPercentage) / 100; + $incomeForIsr = $monthlySalary + $commission; + $isrTable = $month->isrTable; + $isrResult = IsrCalculator::calculateMonthly($incomeForIsr, $isrTable); + $isrMonthlyAmount = $isrResult['isr']; + $isrBiweekly = IsrCalculator::calculateBiweekly($isrMonthlyAmount); + if ($biweekly === 1) { // ===================== // QUINCENA 1 - ANTICIPO // ===================== - // Anticipo = mitad del sueldo + comisiones del MES completo - $totalSystemSales = $month->dailySales()->sum('system_sales'); - $commission = ($totalSystemSales * $commissionPercentage) / 100; + // Anticipo = mitad del sueldo + comisiones del MES completo - ISR quincenal // Gastos: q1 completo + mensual/2 $expensesQ1Amount = $month->expenses() @@ -97,7 +114,7 @@ class CommissionCalculator return $e->expense_type === 'mensual' ? $e->amount / 2 : $e->amount; }); - $totalEarning = $biweeklySalary + $commission - $expensesQ1Amount; + $totalEarning = $biweeklySalary + $commission - $expensesQ1Amount - $isrBiweekly; return [ 'user_id' => $user->id, @@ -105,11 +122,12 @@ class CommissionCalculator 'month_name' => $month->name . ' ' . $year, 'biweekly' => $biweekly, 'period' => '1ra Quincena (1-15) - ANTICIPO', - 'description' => 'Mitad del sueldo + comisiones del mes completo', + 'description' => 'Mitad del sueldo + comisiones del mes completo - ISR', 'biweekly_salary' => round($biweeklySalary, 2), 'total_system_sales' => round($totalSystemSales, 2), 'commission_percentage' => round($commissionPercentage, 2), 'commission_amount' => round($commission, 2), + 'isr_amount' => round($isrBiweekly, 2), 'total_expenses_month' => 0, 'expenses_q1' => round($expensesQ1Amount, 2), 'expenses_q2' => 0, @@ -120,7 +138,7 @@ class CommissionCalculator // ===================== // QUINCENA 2 - LIQUIDACIÓN // ===================== - // Liquidación = mitad del sueldo - gastos de Q2 + // Liquidación = mitad del sueldo - gastos de Q2 - ISR quincenal // Gastos: q2 completo + mensual/2 $expensesQ2Amount = $month->expenses() @@ -134,7 +152,7 @@ class CommissionCalculator }); // Total a pagar en liquidacion - $totalEarning = $biweeklySalary - $expensesQ2Amount; + $totalEarning = $biweeklySalary - $expensesQ2Amount - $isrBiweekly; return [ 'user_id' => $user->id, @@ -142,9 +160,10 @@ class CommissionCalculator 'month_name' => $month->name . ' ' . $year, 'biweekly' => $biweekly, 'period' => "2da Quincena (16-$lastDay) - LIQUIDACIÓN", - 'description' => 'Mitad del sueldo - mitad de gastos del mes', + 'description' => 'Mitad del sueldo - mitad de gastos del mes - ISR', 'biweekly_salary' => round($biweeklySalary, 2), 'total_system_sales' => 0, + 'isr_amount' => round($isrBiweekly, 2), 'expenses_q2' => round($expensesQ2Amount, 2), 'total_earning' => round($totalEarning, 2), 'type' => 'liquidacion', diff --git a/app/Services/IsrCalculator.php b/app/Services/IsrCalculator.php new file mode 100644 index 0000000..07492f5 --- /dev/null +++ b/app/Services/IsrCalculator.php @@ -0,0 +1,65 @@ + float, 'bracket' => array|null, 'effective_rate' => float] + */ + public static function calculateMonthly(float $baseIncome, ?IsrTable $isrTable): array + { + if (!$isrTable || $baseIncome <= 0) { + return [ + 'isr' => 0, + 'bracket' => null, + 'effective_rate' => 0, + 'taxable_income' => 0, + ]; + } + + $brackets = $isrTable->brackets()->orderBy('lower_limit')->get(); + + foreach ($brackets as $bracket) { + $upperLimit = $bracket->upper_limit; + + // Si es el último bracket (upper_limit es null = "En adelante") + if ($upperLimit === null || $baseIncome <= $upperLimit) { + // Calcular ISR: Cuota Fija + (Excedente × Tasa) + $excedent = $baseIncome - $bracket->lower_limit; + $isr = $bracket->fixed_fee + ($excedent * $bracket->rate / 100); + + return [ + 'isr' => round($isr, 2), + 'bracket' => [ + 'lower_limit' => $bracket->lower_limit, + 'upper_limit' => $bracket->upper_limit, + 'fixed_fee' => $bracket->fixed_fee, + 'rate' => $bracket->rate, + ], + 'effective_rate' => round(($isr / $baseIncome) * 100, 2), + 'taxable_income' => round($baseIncome, 2), + ]; + } + } + + return [ + 'isr' => 0, + 'bracket' => null, + 'effective_rate' => 0, + 'taxable_income' => 0, + ]; + } + + /** + * Calcular ISR quincenal = ISR mensual / 2 + */ + public static function calculateBiweekly(float $monthlyIsr): float + { + return round($monthlyIsr / 2, 2); + } +} diff --git a/database/migrations/2026_04_21_000002_add_isr_table_id_to_months_table.php b/database/migrations/2026_04_21_000002_add_isr_table_id_to_months_table.php new file mode 100644 index 0000000..3a46fb4 --- /dev/null +++ b/database/migrations/2026_04_21_000002_add_isr_table_id_to_months_table.php @@ -0,0 +1,29 @@ +foreignId('isr_table_id')->nullable()->constrained('isr_tables')->nullOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('months', function (Blueprint $table) { + $table->dropForeign(['isr_table_id']); + $table->dropColumn('isr_table_id'); + }); + } +}; diff --git a/resources/views/months/create.blade.php b/resources/views/months/create.blade.php index 905b786..3238c39 100755 --- a/resources/views/months/create.blade.php +++ b/resources/views/months/create.blade.php @@ -41,6 +41,19 @@ value="{{ old('year', now()->year) }}" min="2020" max="2100" required> +
+ + + Selecciona la tabla ISR a aplicar en este mes +
+
Cancelar diff --git a/resources/views/months/edit.blade.php b/resources/views/months/edit.blade.php index af06356..bc87184 100755 --- a/resources/views/months/edit.blade.php +++ b/resources/views/months/edit.blade.php @@ -41,6 +41,19 @@
+
+ + + Selecciona la tabla ISR a aplicar en este mes +
+
Cancelar diff --git a/resources/views/months/index.blade.php b/resources/views/months/index.blade.php index 99590f3..30dc155 100755 --- a/resources/views/months/index.blade.php +++ b/resources/views/months/index.blade.php @@ -36,11 +36,20 @@ Pagado @endif

+

+ ISR: + @if($month->isrTable) + {{ $month->isrTable->year }} + @else + Sin ISR + @endif +

Ventas: ${{ number_format($month->dailySales()->sum('user_sales'), 2) }}

Gastos: ${{ number_format($month->expenses()->sum('amount'), 2) }}

diff --git a/resources/views/reports/biweekly.blade.php b/resources/views/reports/biweekly.blade.php index c53238a..4f2a374 100755 --- a/resources/views/reports/biweekly.blade.php +++ b/resources/views/reports/biweekly.blade.php @@ -48,31 +48,53 @@
@if($report['type'] === 'anticipo') -
+
Mitad Sueldo

${{ number_format($report['biweekly_salary'], 2) }}

-
+
Comisiones del Mes

+${{ number_format($report['commission_amount'], 2) }}

-
+ @if(isset($report['isr_amount']) && $report['isr_amount'] > 0) +
+
ISR Quincenal
+

-${{ number_format($report['isr_amount'], 2) }}

+
+
Total ANTICIPO

${{ number_format($report['total_earning'], 2) }}

+ @else +
+
Total ANTICIPO
+

${{ number_format($report['total_earning'], 2) }}

+
+ @endif @else -
+
Mitad Sueldo

${{ number_format($report['biweekly_salary'], 2) }}

-
+
Gastos Q{{ $biweekly }}

-${{ number_format($report['expenses_q2'], 2) }}

-
+ @if(isset($report['isr_amount']) && $report['isr_amount'] > 0) +
+
ISR Quincenal
+

-${{ number_format($report['isr_amount'], 2) }}

+
+
Total LIQUIDACIÓN

${{ number_format($report['total_earning'], 2) }}

+ @else +
+
Total LIQUIDACIÓN
+

${{ number_format($report['total_earning'], 2) }}

+
+ @endif @endif
diff --git a/resources/views/reports/monthly.blade.php b/resources/views/reports/monthly.blade.php index ad0d187..af5f235 100755 --- a/resources/views/reports/monthly.blade.php +++ b/resources/views/reports/monthly.blade.php @@ -78,6 +78,12 @@ Gastos del Mes -${{ number_format($report['total_expenses'], 2) }} + @if(isset($report['isr_amount']) && $report['isr_amount'] > 0) + + ISR ({{ $report['isr_details']['effective_rate'] ?? 0 }}%) + -${{ number_format($report['isr_amount'], 2) }} + + @endif Total a Recibir ${{ number_format($report['total_earning'], 2) }}