Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
11.84% covered (danger)
11.84%
47 / 397
7.14% covered (danger)
7.14%
1 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
GestionaController
11.84% covered (danger)
11.84%
47 / 397
7.14% covered (danger)
7.14%
1 / 14
3724.56
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
1 / 1
1
 updateApiCredentials
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 getApiDetails
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 getLastUpdate
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 getAcceptanceWarnings
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
42
 getSyncStatus
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 getG3wActive
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 updateG3wActive
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getMappings
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 setMappings
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
210
 deleteMappings
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
 getAllBudgetMonitor
0.00% covered (danger)
0.00%
0 / 116
0.00% covered (danger)
0.00%
0 / 1
182
 syncAllBudgetMonitor
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
42
 getDuplicatedValues
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Exceptions\AppException;
6use App\Models\TblCompanies;
7use App\Models\TblG3WOrdersUpdateLogs;
8use App\Models\TblQuotations;
9use App\Models\TblSegmentG3wMapping;
10use App\Models\TblSourceG3wMapping;
11use App\Models\TblSources;
12use App\Models\TblStatusG3wMapping;
13use App\Models\TblTypeG3wMapping;
14use App\Models\TblUserG3wMapping;
15use App\Models\TblUsers;
16use App\Services\GestionaService;
17use App\Services\PresupuestosService;
18use Illuminate\Database\QueryException;
19use Illuminate\Http\JsonResponse;
20use Illuminate\Http\Request;
21use Illuminate\Support\Carbon;
22use Illuminate\Support\Facades\DB;
23use Illuminate\Support\Facades\Log;
24use Illuminate\Support\Facades\Schema;
25
26class GestionaController extends Controller
27{
28    protected $tables;
29
30    public function __construct(protected GestionaService $gestionaService, protected PresupuestosService $presupuestoService)
31    {
32        $this->tables = [
33            'status' => [
34                'model' => TblStatusG3wMapping::class,
35                'index' => 'budget_status_id',
36                'relation' => 'budgetStatus',
37                'tblQuotationsG3wNameColumn' => 'status_by_g3w',
38                'tblMappingNameColumn' => 'name_g3w',
39                'tblQuotationsNameColumn' => 'budget_status_id',
40                'requestNameKey' => 'budget_status_id',
41            ],
42            'segment' => [
43                'model' => TblSegmentG3wMapping::class,
44                'index' => 'segment_id',
45                'relation' => 'segment',
46                'tblQuotationsG3wNameColumn' => 'segment_by_g3w',
47                'tblMappingNameColumn' => 'name_g3w',
48                'tblQuotationsNameColumn' => 'segment_id',
49                'requestNameKey' => 'segment_id',
50            ],
51            'type' => [
52                'model' => TblTypeG3wMapping::class,
53                'index' => 'budget_type_name',
54                'relation' => 'budgetType',
55                'tblQuotationsG3wNameColumn' => 'type_by_g3w',
56                'tblMappingNameColumn' => 'id_g3w',
57                'tblQuotationsNameColumn' => 'budget_type_id',
58                'requestNameKey' => 'id_g3w',
59            ],
60            'source' => [
61                'model' => TblSourceG3wMapping::class,
62                'index' => 'source_name',
63                'relation' => 'source',
64                'tblQuotationsG3wNameColumn' => 'source_by_g3w',
65                'tblMappingNameColumn' => 'id_g3w',
66                'tblQuotationsNameColumn' => 'source_id',
67                'requestNameKey' => 'id_g3w',
68            ],
69            'user' => [
70                'model' => TblUserG3wMapping::class,
71                'index' => 'id_fst',
72                'relation' => 'user',
73                'tblQuotationsG3wNameColumn' => 'G3W_code',
74                'tblMappingNameColumn' => 'id_fst',
75                'tblQuotationsNameColumn' => 'G3W_code',
76                'requestNameKey' => 'id_fst',
77            ],
78        ];
79    }
80
81    /**
82     * Sincroniza un presupuesto por su ID.
83     *
84     * @param  $id
85     * @return JsonResponse
86     */
87    public function updateApiCredentials(Request $request)
88    {
89        try {
90            $region = request()->header('Region');
91
92            if ($region == 'Catalunya') {
93                $region = 'Cataluña';
94            }
95
96            $params = $request->only(['url', 'user', 'password', 'user_id']);
97            $this->gestionaService->updateApiCredentials($params, $region);
98
99            return response()->json(['message' => 'Credentials updated successfully'], 200);
100        } catch (\Exception $e) {
101            report(AppException::fromException($e, 'UPDATE_API_CREDENTIALS_EXCEPTION'));
102
103            return response()->json(['error' => $e->getMessage()], 400);
104        }
105    }
106
107    /**
108     * @return JsonResponse
109     *
110     * @throws \Exception
111     */
112    public function getApiDetails()
113    {
114        try {
115            $region = request()->header('Region');
116
117            if ($region === 'Catalunya') {
118                $region = 'Cataluña';
119            }
120
121            $apiDetailsResponse = $this->gestionaService->getApiDetails($region);
122
123            if (! $apiDetailsResponse) {
124                throw new \Exception('Api details not found');
125            }
126
127            $apiDetails = json_decode((string) $apiDetailsResponse->getContent(), true);
128
129            return response()->json([
130                'url' => $apiDetails['url'] ?? null,
131                'user' => $apiDetails['user'] ?? null,
132            ], 200);
133
134        } catch (QueryException $e) {
135            report(AppException::fromException($e, 'GET_API_DETAILS_EXCEPTION'));
136
137            return response()->json(['error' => $e->getMessage()], 400);
138        }
139    }
140
141    public function getLastUpdate()
142    {
143        try {
144            $region = request()->header('Region');
145
146            if ($region === 'Catalunya') {
147                $region = 'Cataluña';
148            }
149
150            $apiLastUpdateResponse = $this->gestionaService->getLastUpdate($region);
151
152            if (! $apiLastUpdateResponse) {
153                throw new \Exception('Last update not found');
154            }
155
156            $apiLastUpdate = json_decode((string) $apiLastUpdateResponse->getContent(), true);
157
158            return response()->json([
159                'lastUpdate' => $apiLastUpdate['lastUpdate'] ?? null,
160                'updatingNow' => $apiLastUpdate['updatingNow'] ?? null,
161            ], 200);
162
163        } catch (QueryException $e) {
164            report(AppException::fromException($e, 'GET_LAST_UPDATE_EXCEPTION'));
165            Log::error('Error en getLastUpdate: '.$e->getMessage());
166
167            return response()->json(['error' => $e->getMessage()], 400);
168        }
169    }
170
171    public function getAcceptanceWarnings(Request $request)
172    {
173        try {
174            $region = request()->header('Region') ?? null;
175
176            if (! $region) {
177                return response()->json(['error' => 'Region header is missing'], 400);
178            }
179
180            if ($region == 'Catalunya') {
181                $region = 'Cataluña';
182            }
183
184            $commercial = $request->input('commercial');
185
186            $company = TblCompanies::where('region', $region)->first();
187            if (! $company) {
188                return response()->json([
189                    'countWarnings' => 0,
190                ], 200);
191            }
192
193            $companyId = $company->company_id;
194
195            /*$countWarnings = TblQuotations::where('sync_import', 1)
196                ->where('company_id', $companyId)
197                ->when(!empty($commercial) && $commercial !== "All", function ($query) use ($commercial) {
198                    return $query->where('commercial', $commercial);
199                })
200                ->where(function ($query) {
201                    $query->WhereIn('budget_status_id', [13, 14])
202                        ->orWhere(function ($subQuery) {
203                            $subQuery->whereNull('commercial')
204                                ->orWhereNotExists(function ($subQuery) {
205                                    $subQuery->select(DB::raw(1))
206                                        ->from('tbl_users')
207                                        ->whereColumn('tbl_users.name', 'tbl_quotations.commercial');
208                                });
209                        })
210                        ->orWhereNull('phone_number')
211                        ->orWhereNull('source_id')
212                        ->orWhereNull('budget_type_id')
213                        ->orWhere(function ($subQuery) {
214                            $subQuery->whereNull('client')
215                                ->orWhere(DB::raw('TRIM(client)'), '');
216                        })
217                        ->orWhere(function ($subQuery) {
218                            $subQuery->whereNull('email')
219                                ->orWhere(DB::raw('TRIM(email)'), '');
220                        });
221                })
222                ->count();*/
223
224            $countWarnings = TblQuotations::where('sync_import', 1)
225                ->where('company_id', $companyId)
226                ->where('g3w_warning', 1)
227                ->when(! empty($commercial) && $commercial !== 'All', fn ($query) => $query->where('commercial', $commercial))
228                ->count();
229
230            return response()->json([
231                'countWarnings' => $countWarnings,
232            ], 200);
233
234        } catch (QueryException $e) {
235            report(AppException::fromException($e, 'ADD_WORK_STATUS_EXCEPTION'));
236            Log::error('Error en getAcceptanceWarnings: '.$e->getMessage());
237
238            return response()->json(['error' => $e->getMessage()], 400);
239        }
240    }
241
242    public function getSyncStatus()
243    {
244        try {
245            $region = request()->header('Region');
246
247            if ($region === 'Catalunya') {
248                $region = 'Cataluña';
249            }
250
251            return response()->json([
252                'status' => $this->gestionaService->getSyncStatus($region),
253            ], 200);
254
255        } catch (QueryException $e) {
256            report(AppException::fromException($e, 'GET_SYNC_STATUS_EXCEPTION'));
257
258            return response()->json(['error' => $e->getMessage()], 400);
259        }
260    }
261
262    public function getG3wActive()
263    {
264        try {
265            $region = request()->header('Region');
266
267            $region = urldecode((string) $region);
268
269            if ($region === 'Catalunya') {
270                $region = 'Cataluña';
271            }
272
273            return response()->json([
274                'active' => $this->gestionaService->getG3wActive($region),
275            ], 200);
276
277        } catch (QueryException $e) {
278            report(AppException::fromException($e, 'GET_G3W_ACTIVE_EXCEPTION'));
279            Log::error('Error en getG3wActive: '.$e->getMessage());
280
281            return response()->json(['error' => $e->getMessage()], 400);
282        }
283    }
284
285    public function updateG3wActive(Request $request)
286    {
287        try {
288            $region = urldecode($request->header('Region'));
289
290            if ($region === 'Catalunya') {
291                $region = 'Cataluña';
292            }
293
294            $active = $request->input('active');
295
296            $this->gestionaService->updateG3wActive($active, $region);
297
298            return response()->json([
299                'success' => 1,
300            ], 200);
301
302        } catch (QueryException $e) {
303            report(AppException::fromException($e, 'UPDATE_G3W_ACTIVE_EXCEPTION'));
304
305            return response()->json(['error' => $e->getMessage()], 400);
306        }
307    }
308
309    public function getMappings(Request $request)
310    {
311        try {
312
313            if (! $request->has('type') || ! array_key_exists($request->type, $this->tables)) {
314                throw new \Exception('Type parameter is missing or invalid');
315            }
316
317            $tableConfig = $this->tables[$request->type];
318
319            if (isset($tableConfig['model'])) {
320                $mappings = $tableConfig['model']::with([$tableConfig['relation'] => function ($query) use ($tableConfig): void {
321                    $relatedModel = (new $tableConfig['model'])->{$tableConfig['relation']}()->getRelated();
322                    $relatedTable = $relatedModel->getTable();
323
324                    if (Schema::hasColumn($relatedTable, 'priority')) {
325                        $query->orderBy('priority', 'desc');
326                    }
327                }])->get();
328            } else {
329                $mappings = DB::table($tableConfig['table'])->get();
330            }
331
332            return response()->json($mappings);
333
334        } catch (\Exception $e) {
335            report(AppException::fromException($e, 'GET_MAPPINGS_EXCEPTION'));
336
337            return response()->json(['error' => $e->getMessage()], 400);
338        }
339    }
340
341    public function setMappings(Request $request)
342    {
343        try {
344            if (! $request->has('type')) {
345                return response()->json(['message' => 'Not type especified']);
346            }
347
348            $tableConfig = $this->tables[$request->type];
349
350            if (! isset($tableConfig['tblQuotationsG3wNameColumn'], $tableConfig['tblMappingNameColumn'], $tableConfig['tblQuotationsNameColumn'], $tableConfig['requestNameKey'])) {
351                return response()->json(['message' => 'Missing configuration keys'], 500);
352            }
353
354            $data = $request->except('type');
355
356            if (! $request->has('id')) {
357                $tableConfig['model']::create($data);
358
359                return response()->json(['message' => $request->type.' created successfully']);
360            }
361
362            if ($tableConfig['relation'] === 'user') {
363                if (is_numeric($request['id'])) {
364                    $rowMapping = $tableConfig['model']::where('id_fst', $request['id'])->first();
365                    $data = $request->except('id');
366
367                    if (! $rowMapping) {
368                        $tableConfig['model']::create($data);
369
370                        return response()->json(['error' => 'User mapping not found'], 404);
371                    }
372
373                    $userName = TblUsers::where('id', $rowMapping->id_fst)->first()->name;
374                    $nameG3w = $rowMapping->name_g3w;
375
376                    TblQuotations::where('user_create_by_g3w', $nameG3w)
377                        ->update(['created_by' => $userName]);
378
379                    TblQuotations::where('user_commercial_by_g3w', $nameG3w)
380                        ->update(['commercial' => $userName]);
381
382                    TblQuotations::where('user_create_by_g3w', $nameG3w)
383                        ->whereNull('commercial')
384                        ->whereNull('user_commercial_by_g3w')
385                        ->update(['commercial' => $nameG3w]);
386
387                    $rowMapping->update($data);
388
389                } else {
390                    $rowMapping = $tableConfig['model']::where('name_g3w', $request['id'])->first();
391
392                    if (! $rowMapping) {
393                        return response()->json(['error' => 'User mapping not found'], 404);
394                    }
395
396                    $rowMapping->update(['id_fst' => $request['id_fst']]);
397
398                    $userName = TblUsers::where('id', $request['id_fst'])->first();
399
400                    if (! $userName) {
401                        return response()->json(['error' => 'User mapping not found'], 404);
402                    }
403
404                    TblQuotations::where('user_create_by_g3w', $request['id'])
405                        ->update(['created_by' => $userName->name]);
406
407                    TblQuotations::where('user_commercial_by_g3w', $request['id'])
408                        ->update(['commercial' => $userName->name]);
409
410                    TblQuotations::where('user_create_by_g3w', $request['id'])
411                        ->whereNull('commercial')
412                        ->whereNull('user_commercial_by_g3w')
413                        ->update(['commercial' => $userName->name]);
414
415                }
416
417                return response()->json(['message' => $request->type.' edited successfully']);
418            }
419
420            $rowMapping = $tableConfig['model']::where('id', $request['id'])->first();
421
422            if ($tableConfig['relation'] === 'source') {
423                $data = [];
424                $sourceRow = TblSources::where('source_id', $request['id_fst'])->first();
425                if (! $sourceRow) {
426                    return response()->json(['error' => 'Source mapping not found'], 404);
427                }
428
429                $data['source_name'] = $sourceRow->name;
430            }
431
432            $rowMapping->update($data);
433
434            $g3wKey = $rowMapping[$tableConfig['tblMappingNameColumn']] ?? null;
435
436            // A null/empty G3W key would collapse Eloquent's `where(col, null)`
437            // to `WHERE col IS NULL` and null tbl_quotations.budget_type_id
438            // (or source_id) on every row whose corresponding *_by_g3w column
439            // is unset — i.e. every manually-created quotation. Skip the
440            // propagation when the mapping has no key to match against.
441            if ($g3wKey === null || $g3wKey === '') {
442                return response()->json(['message' => $request->type.' edited successfully']);
443            }
444
445            TblQuotations::where($tableConfig['tblQuotationsG3wNameColumn'], $g3wKey)
446                ->update([$tableConfig['tblQuotationsNameColumn'] => $rowMapping[$tableConfig['requestNameKey']]]);
447
448            return response()->json(['message' => $request->type.' edited successfully']);
449
450        } catch (QueryException $e) {
451            report(AppException::fromException($e, 'SET_MAPPING_EXCEPTION'));
452
453            return response()->json(['error' => $e->getMessage()], 400);
454        }
455    }
456
457    public function deleteMappings(Request $request)
458    {
459        try {
460            if (! $request->has('id')) {
461                return response()->json(['message' => 'Can`t be deleted without ID']);
462            }
463
464            if (! $request->has('type')) {
465                return response()->json(['message' => 'Not type especified']);
466            }
467
468            $tableConfig = $this->tables[$request->type];
469
470            if ($tableConfig['relation'] === 'user') {
471                $tableConfig['model']::where('id_fst', $request['id'])->delete();
472
473                return response()->json(['message' => $request->type.' deleted successfully']);
474            }
475
476            $tableConfig['model']::where('id', $request['id'])->delete();
477
478            return response()->json(['message' => $request->type.' deleted successfully']);
479
480        } catch (QueryException $e) {
481            report(AppException::fromException($e, 'DELETE_MAPPING_EXCEPTION'));
482
483            return response()->json(['error' => $e->getMessage()], 400);
484        }
485    }
486
487    public function getAllBudgetMonitor(Request $request)
488    {
489        try {
490            $data = $request->all();
491            $region = urldecode((string) request()->header('Region'));
492            $page = $data['page'];
493
494            $daysOffset = $page * 10;
495            $endDate = Carbon::today()->subDays($daysOffset);
496            $startDate = $endDate->copy()->addDays(10);
497
498            $dataResponse = [];
499            $currentDate = $startDate->copy();
500            $companyId = TblCompanies::where('region', $region)->first()->company_id;
501            $alreadyG3wBudgets = [];
502
503            while ($currentDate > $endDate) {
504                $dateStr = $currentDate->format('Y-m-d');
505
506                $g3wBudgets = $this->gestionaService->getBudgetsByDay($dateStr, $region);
507                if (is_string($g3wBudgets)) {
508                    $g3wBudgets = json_decode($g3wBudgets, true);
509                }
510
511                $g3wBudgetIds = array_map(fn (array $item) => $item['ID'], $g3wBudgets);
512
513                $newG3wBudgetIds = array_diff($g3wBudgetIds, $alreadyG3wBudgets);
514                $alreadyG3wBudgets = array_merge($alreadyG3wBudgets, $newG3wBudgetIds);
515                $countG3wBudgets = count($newG3wBudgetIds);
516
517                $fstBudgets = TblQuotations::where('company_id', $companyId)
518                    ->where(function ($query): void {
519                        $query->where('sync_import', 1)
520                            ->orWhere('sync_import_edited', 1);
521                    })
522                    ->whereDate('created_at', $dateStr)
523                    ->pluck('internal_quote_id')
524                    ->toArray();
525
526                if ($companyId == 18 || $companyId == 22) {
527                    $fstBudgets = TblQuotations::whereIn('company_id', [18, 22])
528                        ->where(function ($query): void {
529                            $query->where('sync_import', 1)
530                                ->orWhere('sync_import_edited', 1);
531                        })
532                        ->whereDate('created_at', $dateStr)
533                        ->pluck('internal_quote_id')
534                        ->toArray();
535                }
536
537                $countFstBudgets = count($fstBudgets);
538
539                $missingIds = array_diff($newG3wBudgetIds, $fstBudgets);
540
541                $existingIds = TblQuotations::where('company_id', $companyId)
542                    ->where(function ($query): void {
543                        $query->where('sync_import', 1)
544                            ->orWhere('sync_import_edited', 1);
545                    })
546                    ->whereIn('internal_quote_id', $missingIds)
547                    ->pluck('internal_quote_id')
548                    ->toArray();
549
550                if ($companyId == 18 || $companyId == 22) {
551                    $existingIds = TblQuotations::where('company_id', $companyId)
552                        ->where(function ($query): void {
553                            $query->where('sync_import', 1)
554                                ->orWhere('sync_import_edited', 1);
555                        })
556                        ->whereIn('internal_quote_id', $missingIds)
557                        ->pluck('internal_quote_id')
558                        ->toArray();
559                }
560
561                $finalMissingIds = array_diff($missingIds, $existingIds);
562
563                $duplicatedFst = $this->getDuplicatedValues($fstBudgets);
564
565                $deletedIds = array_diff($fstBudgets, $newG3wBudgetIds);
566                foreach ($deletedIds as $key => $id) {
567                    $deleted = $this->gestionaService->checkDeleted($id, $region);
568                    if (! $deleted) {
569                        unset($deletedIds[$key]);
570                    }
571                }
572
573                $g3wOrdersUpdateLogs = TblG3WOrdersUpdateLogs::where('company_id', $companyId)
574                    ->whereDate('ended_at', $dateStr)
575                    ->where('processed_by', 'System')
576                    ->get();
577
578                $syncs = $g3wOrdersUpdateLogs->count();
579
580                $allUniqueErrorIds = [];
581
582                $syncErrorIds = $g3wOrdersUpdateLogs->flatMap(function ($item): array {
583                    $ids = $item->sync_error_ids;
584                    if (is_string($ids)) {
585                        $decoded = json_decode($ids, true);
586                        $ids = is_array($decoded) ? $decoded : explode(',', $ids);
587                    }
588
589                    return (array) $ids;
590                })
591                    ->filter()
592                    ->unique()
593                    ->toArray();
594
595                $allUniqueErrorIds = array_merge($allUniqueErrorIds, $syncErrorIds);
596
597                $finalUniqueIds = array_values(array_unique($allUniqueErrorIds));
598                $countSyncErrorIds = count($finalUniqueIds);
599
600                $dataResponse[] = [
601                    'date' => $dateStr,
602                    'g3wBudgets' => array_values($newG3wBudgetIds),
603                    'countG3wBudgets' => $countG3wBudgets,
604                    'fstBudgets' => $fstBudgets,
605                    'countfstBudgets' => $countFstBudgets,
606                    'missingIds' => array_values($finalMissingIds),
607                    'duplicatedFst' => $duplicatedFst,
608                    'deletedIds' => array_values($deletedIds),
609                    'syncs' => $syncs,
610                    'syncErrorIds' => $syncErrorIds,
611                    'countSyncErrorIds' => $countSyncErrorIds,
612                ];
613
614                $currentDate->subDay();
615
616            }
617
618            usort($dataResponse, fn (array $a, array $b) => strtotime($b['date']) - strtotime($a['date']));
619
620            return response()->json([
621                'data' => $dataResponse,
622                'meta' => [
623                    'current_page' => (int) $page,
624                    'date_range' => [
625                        'start' => $startDate->format('Y-m-d'),
626                        'end' => $endDate->format('Y-m-d'),
627                    ],
628                ]]);
629        } catch (\Exception $e) {
630            report(AppException::fromException($e, 'GET_ALL_BUDGET_MONITOR_EXCEPTION'));
631            Log::error('Error en getAllBudgetMonitor: '.$e->getMessage());
632
633            if ($e->getMessage() == 'API URL is not defined.') {
634                return response()->json([
635                    'error' => 'KO',
636                    'message' => 'La conexión con la API de G3W no está configurada. Por favor, configúrala en los ajustes de la empresa.',
637                ], 200);
638            }
639
640            return response()->json([
641                'message' => 'Error interno del servidor',
642            ], 500);
643        }
644    }
645
646    public function syncAllBudgetMonitor(Request $request)
647    {
648        try {
649            $data = $request->all();
650            if (app()->runningInConsole()) {
651                $region = $request->header('Region');
652            } else {
653                $region = urldecode(request()->header('Region') ?? '');
654            }
655            $day = $data['day'];
656            $dataResponse = [];
657            $companyId = TblCompanies::where('region', $region)->first()->company_id;
658            $alreadyG3wBudgets = [];
659
660            $g3wBudgets = $this->gestionaService->getBudgetsByDay($day, $region);
661
662            if (is_string($g3wBudgets)) {
663                $g3wBudgets = json_decode($g3wBudgets, true);
664            }
665
666            $g3wBudgetIds = array_map(fn (array $item) => $item['ID'], $g3wBudgets);
667
668            $newG3wBudgetIds = array_diff($g3wBudgetIds, $alreadyG3wBudgets);
669            $alreadyG3wBudgets = array_merge($alreadyG3wBudgets, $newG3wBudgetIds);
670
671            $fstBudgets = TblQuotations::where('company_id', $companyId)
672                ->where('sync_import', 1)
673                ->whereDate('created_at', $day)
674                ->pluck('internal_quote_id')
675                ->toArray();
676
677            $missingIds = array_diff($newG3wBudgetIds, $fstBudgets);
678
679            $existingIds = TblQuotations::where('company_id', $companyId)
680                ->where('sync_import', 1)
681                ->whereIn('internal_quote_id', $missingIds)
682                ->pluck('internal_quote_id')
683                ->toArray();
684
685            $finalMissingIds = array_diff($missingIds, $existingIds);
686
687            $duplicatedFst = $this->getDuplicatedValues($fstBudgets);
688
689            $deletedIds = array_diff($fstBudgets, $newG3wBudgetIds);
690
691            foreach ($deletedIds as $key => $id) {
692                $deleted = $this->gestionaService->checkDeleted($id, $region);
693                if (! $deleted) {
694                    unset($deletedIds[$key]);
695                }
696            }
697
698            $this->presupuestoService->syncByIds(implode(',', $finalMissingIds), $region, $day);
699
700            $dataResponse[] = [
701                'missingIds' => array_values($finalMissingIds),
702                'deletedIds' => array_values($deletedIds),
703            ];
704
705            return response()->json([
706                'success' => true,
707                'message' => 'Presupuestos que faltan sincronizados correctamente',
708            ]);
709        } catch (\Exception $e) {
710            report(AppException::fromException($e, 'SYNC_ALL_BUDGET_MONITOR_EXCEPTION'));
711            Log::error('Error en getAllBudgetMonitor: '.$e->getMessage());
712
713            return response()->json([
714                'message' => 'Error interno del servidor',
715            ], 500);
716        }
717    }
718
719    /**
720     * @return int[]|string[]
721     */
722    private function getDuplicatedValues($fstBudgets): array
723    {
724        $filteredValues = array_filter($fstBudgets, fn ($value) => $value !== null);
725
726        if (empty($filteredValues)) {
727            return [];
728        }
729
730        $counts = array_count_values($filteredValues);
731        $duplicates = array_filter($counts, fn ($count) => $count > 1);
732
733        return array_keys($duplicates);
734    }
735}