diff --git a/app/Jobs/SendScheduledReport.php b/app/Jobs/SendScheduledReport.php new file mode 100644 index 0000000..690920b --- /dev/null +++ b/app/Jobs/SendScheduledReport.php @@ -0,0 +1,48 @@ +whereHas('projects') + ->with('projects') + ->get(); + + foreach ($users as $user) { + foreach ($user->projects as $project) { + $filters = new ReportFilters(); + $filters->entityTypes = ['phases', 'features', 'inspections', 'issues', 'tasks']; + + $generator = new ReportGenerator($project, $filters); + $data = $generator->generate(); + + Mail::to($user)->send(new \App\Mail\ScheduledReportMail( + $project, + $user, + $data, + $filters + )); + } + } + } +} \ No newline at end of file diff --git a/app/Mail/ScheduledReportMail.php b/app/Mail/ScheduledReportMail.php new file mode 100644 index 0000000..ee6f9bd --- /dev/null +++ b/app/Mail/ScheduledReportMail.php @@ -0,0 +1,44 @@ +project->name}", + ); + } + + public function content(): Content + { + return new Content( + view: 'emails.scheduled-report', + with: [ + 'project' => $this->project, + 'user' => $this->user, + 'data' => $this->reportData, + 'filters' => $this->filters, + ], + ); + } +} \ No newline at end of file diff --git a/database/migrations/2026_09_01_111914_add_receive_reports_to_users_table.php b/database/migrations/2026_09_01_111914_add_receive_reports_to_users_table.php new file mode 100644 index 0000000..3441c5a --- /dev/null +++ b/database/migrations/2026_09_01_111914_add_receive_reports_to_users_table.php @@ -0,0 +1,22 @@ +boolean('receive_reports')->default(false)->after('address'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('receive_reports'); + }); + } +}; \ No newline at end of file diff --git a/resources/views/emails/scheduled-report.blade.php b/resources/views/emails/scheduled-report.blade.php new file mode 100644 index 0000000..0bff609 --- /dev/null +++ b/resources/views/emails/scheduled-report.blade.php @@ -0,0 +1,157 @@ + + + + + + Informe Programado - {{ $project->name }} + + + +
+
+

{{ $project->name }}

+

Informe programado generado el {{ now()->format('d/m/Y H:i') }}

+
+ +
+
+
+
{{ $data['stats']['total_features'] ?? 0 }}
+
Total elementos
+
+
+
{{ $data['stats']['completed_features'] ?? 0 }}
+
Completados
+
+
+
{{ round($data['stats']['avg_progress'] ?? 0) }}%
+
Progreso medio
+
+
+ +
+
Detalle por Fase
+ @forelse($data['phases'] as $phase) + @php + $phaseFeatures = $phase->layers->flatMap(fn($l) => $l->features); + $phaseColor = $phase->color ?? '#3b82f6'; + @endphp +
+
+
+
{{ $phase->name }}
+
+ @if($phase->planned_start) + {{ $phase->planned_start->format('d/m/Y') }} — {{ $phase->planned_end?->format('d/m/Y') ?? 'Sin fecha fin' }} + @else + Sin fechas planificadas + @endif +  •  {{ $phaseFeatures->count() }} elementos +
+
+
+
{{ $phase->progress_percent ?? 0 }}%
+
+
+
+
+
+ @if($phaseFeatures->count() > 0) + + + + + + + + + + + @foreach($phaseFeatures as $feature) + + + + + + + @endforeach + +
ElementoEstadoProgresoResponsable
{{ $feature->name ?? 'Sin nombre' }} + + {{ match($feature->status) { + 'planned' => 'Planificado', + 'started' => 'Iniciado', + 'in_progress' => 'En progreso', + 'completed' => 'Completado', + 'verified' => 'Verificado', + default => ($feature->status ?? 'N/A'), + } }} + + +
+
+
+
+ {{ $feature->progress ?? 0 }}% +
+
{{ $feature->responsible ?? ($feature->responsibleUser?->name ?? '—') }}
+ @else +
+ Sin elementos registrados en esta fase. +
+ @endif +
+ @empty +
+ No hay fases registradas en este proyecto. +
+ @endforelse +
+ +
+
Parámetros del Informe
+

Rango de fechas: {{ $filters->date_from ? $filters->date_from->format('d/m/Y') : 'Sin fecha desde' }} — {{ $filters->date_to ? $filters->date_to->format('d/m/Y') : 'Sin fecha hasta' }}

+

Secciones: {{ implode(', ', collect($filters->entity_types)->map(fn ($k) => ucfirst(str_replace('_', ' ', $k)))->toArray()) }}

+

Incluir fotos: {{ $filters->include_photos ? 'Sí' : 'No' }}

+

Incluir gráficos: {{ $filters->include_charts ? 'Sí' : 'No' }}

+
+
+ + +
+ + \ No newline at end of file diff --git a/routes/console.php b/routes/console.php index 8b0e211..f8b5965 100644 --- a/routes/console.php +++ b/routes/console.php @@ -2,6 +2,7 @@ use App\Jobs\CaptureDailyProgressSnapshot; use App\Jobs\NotifyOverdueTasks; +use App\Jobs\SendScheduledReport; use Illuminate\Foundation\Inspiring; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Schedule; @@ -18,3 +19,6 @@ Schedule::job(new NotifyOverdueTasks)->dailyAt('07:00'); // Capturar snapshot diario de progreso (para curva S, EVM, desvíos) Schedule::job(new CaptureDailyProgressSnapshot)->dailyAt('06:30'); + +// Enviar informes programados diarios (08:00) +Schedule::job(new SendScheduledReport)->dailyAt('08:00');