Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.11% covered (danger)
0.11%
1 / 934
5.00% covered (danger)
5.00%
1 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
FacturasService
0.11% covered (danger)
0.11%
1 / 934
5.00% covered (danger)
5.00%
1 / 20
25677.86
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInvoices
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 loopInvoices
0.00% covered (danger)
0.00%
0 / 148
0.00% covered (danger)
0.00%
0 / 1
380
 loopNextRemindersInvoices
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
56
 loopNextRemindersClients
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 1
240
 sendInvoice
0.00% covered (danger)
0.00%
0 / 132
0.00% covered (danger)
0.00%
0 / 1
600
 getAllInvoices
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 getAllInvoicesExceptions
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 sendCyCInvoices
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
110
 setAllMonthAdministratorsInvoices
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
132
 sendAdministratorsInvoices
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 1
42
 checkClientHasFreshdeskTask
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 checkClientHasFreshdeskTaskAll
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 sendCallCenterInvoices
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 1
210
 addToSheets
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
56
 getGoogleSheetsService
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
56
 writeToGoogleSheet
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 handleGoogleAuthCallback
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
42
 getVencimientosFormateados
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 listCreditDaysOffered
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 1
240
1<?php
2
3namespace App\Services;
4
5use App\Models\TblCompanies;
6use App\Models\TblInvoiceAdministrators;
7use App\Models\TblInvoiceReminders;
8use App\Models\TblInvoiceRemindersEmailTemplate;
9use App\Models\TblInvoicesExceptions;
10use App\Models\TblInvoicesNextReminders;
11use Carbon\Carbon;
12use Google\Service\Sheets;
13use Illuminate\Http\Request;
14use Illuminate\Support\Facades\Http;
15use Illuminate\Support\Facades\Log;
16use Mockery\Exception;
17
18class FacturasService extends GestionaService
19{
20    public function __construct()
21    {
22        parent::__construct();
23    }
24
25    public function getInvoices($region = "Cataluña"): array{
26        try{
27            if(!TblCompanies::where('region', $region)->where('invoice_reminder_active', 1)->exists()){
28                throw new Exception("Sincronizacion no activa para $region");
29            }
30
31            $today = date('Y-m-d');
32            $next10days = date('Y-m-d', strtotime('+10 days'));
33            $lastWeek = date('Y-m-d', strtotime('-1 week'));
34
35            $counter = 1;
36
37            $nextWeekInvoices = $this->request('get', 'factura/vence/'.$next10days, $region, []);
38            // $todayInvoices = $this->request('get', 'factura/vence/' . $today, $region, []);
39            $lastWeekInvoices = $this->request('get', 'factura/vence/'.$lastWeek, $region, []);
40
41            $resultNextWeekInvoices = $this->loopInvoices($nextWeekInvoices, 1, $region, $counter, $next10days);
42            /*$counter = $resultNextWeekInvoices["counter"] - 1;
43            if($counter >= 30){
44                return ['success' => true];
45            }*/
46
47            // $resultTodayInvoices = $this->loopInvoices($todayInvoices, 2, $region, $counter);
48            /*$counter = $resultTodayInvoices["counter"] - 1;
49            if($counter >= 30){
50                return ['success' => true];
51            }*/
52
53            $this->loopInvoices($lastWeekInvoices, 3, $region, $counter, $lastWeek);
54
55            $this->loopNextRemindersInvoices($region);
56
57            $this->loopNextRemindersClients($region, $next10days, $lastWeek);
58
59            return ['success' => true];
60        } catch (\Exception $e) {
61            Log::channel('g3w_invoices')->error($e->getMessage());
62            Log::error('Trace: '.$e->getTraceAsString());
63
64            return ['success' => false, 'error' => $e->getMessage()];
65        }
66    }
67
68    public function loopInvoices(array $invoices, $reminder_type, $region, $counter, $date=null): array{
69        $senders = [];
70        foreach ($invoices['facturas'] as $invoice) {
71            // Continue if region is Comunidad Valenciana and the invoice starts with M
72            if(
73                $region == "Comunidad Valenciana"
74                && str_starts_with((string) $invoice["ID"], 'M')
75            ){
76                continue;
77            }
78
79            // Check if exist a next reminder and jump the loop then
80            $existNextReminderInvoice = TblInvoicesNextReminders::where('invoice_number', $invoice['ID'])
81                ->where('region', $region)
82                ->exists();
83
84            if ($existNextReminderInvoice) {
85                continue;
86            }
87
88            $dataInvoice = $this->request('get', 'factura/'.$invoice['ID'], $region, []);
89
90            if (! isset($dataInvoice['factura'])) {
91                continue;
92            }
93
94            if ($this->checkClientHasFreshdeskTask($dataInvoice['factura']['cod_cliente'], $region)) {
95                continue;
96            }
97
98            $cobrada = $dataInvoice['factura']['cobrada'];
99            $formaPago = $dataInvoice['factura']['forma_pago_factura'];
100
101            // Invoice already paied
102            // Invoice that the payment method is not "transferencia"
103            if (
104                $cobrada !== "NO"
105                || stripos((string) $formaPago, "tr") === false
106            ) {
107                continue;
108            }
109
110            $existNextReminderClient = TblInvoicesNextReminders::where('id_client', $dataInvoice['factura']['cod_cliente'])
111                ->where('region', $region)
112                ->exists();
113
114            if ($existNextReminderClient) {
115                $this->addToSheets($dataInvoice['factura']['cod_cliente'], $dataInvoice['factura']['n_factura'], $region);
116
117                TblInvoicesNextReminders::create([
118                    'id_client' => $dataInvoice['factura']['cod_cliente'],
119                    'region' => $region,
120                    'invoice_number' => $dataInvoice['factura']['n_factura'],
121                ]);
122
123                continue;
124            }
125
126            $dataClient = null;
127
128            if ($dataInvoice['factura']['cod_cliente']) {
129                $dataClient = $this->request('get', 'cliente/'.$dataInvoice['factura']['cod_cliente'], $region, []);
130            }
131
132            if ($dataClient['cliente']['tipo_cliente'] == 'Administrador') {
133                $codService = $dataInvoice['factura']['lineas'][0]['cod_servicio'];
134                $dataService = $this->request('get', 'servicio/'.$codService, $region, []);
135
136                TblInvoiceAdministrators::create([
137                    'invoice_number' => $dataInvoice['factura']['n_factura'],
138                    'region' => $region,
139                    'name' => $dataClient['cliente']['empresa'],
140                    'CIF' => $dataClient['cliente']['cliente_cif'],
141                    'email' => $dataClient['cliente']['email'],
142                    'service_name' => $dataService['servicio']['nombre_servicio'],
143                    'service_addres' => $dataService['servicio']['direccion'],
144                    'send_date' => $date,
145                    'expiration_date' => $date,
146                    'amount' => $dataInvoice["factura"]["importe_total_factura"],
147                ]);
148                continue;
149            }
150
151            $exists = TblInvoicesExceptions::where('cif', $dataClient['cliente']['cliente_cif'])
152                ->orWhere('name', $dataClient['cliente']['empresa'])
153                ->orWhere('administrator', $dataClient['cliente']['empresa'])
154                ->orWhere('id_admin_g3w', $invoice['ID'])
155                ->orWhere('invoice_number', $dataInvoice['factura']['n_factura'])
156                ->exists();
157
158            if ($exists) {
159                continue;
160            }
161
162            $invoice_number = $dataInvoice['factura']['n_factura'] ?? null;
163            $client_name = $dataClient['cliente']['empresa'] ?? null;
164            $client_email = $dataClient['cliente']['email'] ?? $dataClient['cliente']['email_facturacion'] ?? null;
165            $cif = $dataClient['cliente']['cliente_cif'] ?? null;
166            $issued_date = $dataInvoice['factura']['fecha_emision'] ?? null;
167            $expiration_date = $date ?? $dataInvoice['factura']['vencimientos'][0]['fecha_vencimiento'];
168            $amount = $dataInvoice['factura']['importe_total_factura'] ?? null;
169            $collection_date = null;
170            $document = $dataInvoice['factura']['documento'] ?? null;
171            $senders[$cif][] = [
172                'invoice_number' => $invoice_number,
173                'client_name' => $client_name,
174                'client_email' => $client_email,
175                'issued_date' => $issued_date,
176                'expiration_date' => $expiration_date,
177                'document' => $document,
178                'amount' => $amount,
179                'reminder_type' => $reminder_type,
180                'cif' => $cif,
181                'collection_date' => $collection_date,
182                'region' => $region,
183            ];
184
185        }
186
187        if (config("app.env") === "production") {
188            foreach ($senders as $cif => $invoicesGroup) {
189
190                $totalFacturas = count($invoicesGroup);
191
192                if ($totalFacturas === 1) {
193                    $resultSend = $this->sendInvoice(
194                        $invoicesGroup[0]['invoice_number'],
195                        $invoicesGroup[0]['client_name'],
196                        $invoicesGroup[0]['client_email'],
197                        $invoicesGroup[0]['issued_date'],
198                        $invoicesGroup[0]['expiration_date'],
199                        $invoicesGroup[0]['document'],
200                        $invoicesGroup[0]['amount'],
201                        $invoicesGroup[0]['reminder_type'],
202                        $region);
203
204                    if (! $resultSend['success']) {
205                        continue;
206                    }
207
208                    TblInvoiceReminders::create([
209                        'invoice_number' => $invoicesGroup[0]['invoice_number'],
210                        'client_name' => $invoicesGroup[0]['client_name'],
211                        'client_email' => $invoicesGroup[0]['client_email'],
212                        'cif' => $invoicesGroup[0]['cif'],
213                        'issued_date' => $invoicesGroup[0]['issued_date'],
214                        'expiration_date' => $invoicesGroup[0]['expiration_date'],
215                        'amount' => $invoicesGroup[0]['amount'],
216                        'collection_date' => $invoicesGroup[0]['collection_date'],
217                        'region' => $invoicesGroup[0]['region'],
218                        'reminder_type' => $invoicesGroup[0]['reminder_type'],
219                    ]);
220                } else {
221                    $table = "<table style='border-collapse: collapse; width: 100%; font-family: Arial, sans-serif;'>
222                        <tr>
223                            <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Número Factura</th>
224                            <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Nombre cliente de servicio</th>
225                            <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Dirección del servicio</th>
226                            <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Fecha de emisión</th>
227                            <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Fecha de vencimiento</th>
228                            <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Importe</th>
229                        </tr>";
230                    $documentsBase64 = [];
231
232                    foreach ($invoicesGroup as $invoice) {
233                        $invoiceG3W = $this->request('get', 'factura/'.$invoice['invoice_number'], $region, []);
234                        $invoiceData = $invoiceG3W['factura'];
235
236                        $codService = $invoiceData['lineas'][0]['cod_servicio'];
237                        $dataService = $this->request('get', 'servicio/'.$codService, $region, []);
238                        $dataService = $dataService['servicio'];
239
240                        $invoiceNumber = $invoice['invoice_number'];
241                        $table .= "<tr>
242                            <td class='invoice_number' style='border: 1px solid #999; padding: 8px;'>".$invoiceNumber."</td>
243                            <td class='invoice_service_name' style='border: 1px solid #999; padding: 8px;'>".$dataService['nombre_servicio']."</td>
244                            <td class='invoice_service_addres' style='border: 1px solid #999; padding: 8px;'>".$dataService['direccion']."</td>
245                            <td class='invoice_send_date' style='border: 1px solid #999; padding: 8px;'>".Carbon::now()->format('d-m-Y')."</td>
246                            <td class='invoice_expiration_date' style='border: 1px solid #999; padding: 8px;'>".$invoice['expiration_date']."</td>
247                            <td class='invoice_amount' style='border: 1px solid #999; padding: 8px;'>".$invoice['amount'].' €</td>
248                        </tr>';
249
250                        $documentsBase64[] = [
251                            'content' => $invoiceData['documento'],
252                            'filename' => "Factura_{$invoiceNumber}.pdf",
253                        ];
254                    }
255
256                    $table .= '</table>';
257
258                    $this->sendInvoice(
259                        $invoicesGroup[0]['invoice_number'],
260                        $invoicesGroup[0]['client_name'],
261                        $invoicesGroup[0]['client_email'],
262                        Carbon::now()->format('d-m-Y'),
263                        $invoicesGroup[0]['expiration_date'],
264                        $documentsBase64,
265                        $invoicesGroup[0]['amount'],
266                        $invoicesGroup[0]['reminder_type'] === '1' ? 5 : 6,
267                        $region,
268                        $table
269                    );
270
271                }
272
273                $counter++;
274
275            }
276        }
277
278        return [
279            'success' => true,
280            'counter' => $counter,
281        ];
282    }
283
284    private function loopNextRemindersInvoices($region): void{
285        $nextReminders = TblInvoicesNextReminders::where('region', $region)
286            ->where('next_reminders', date('Y-m-d'))
287            ->pluck('invoice_number');
288
289        foreach ($nextReminders as $nextReminder) {
290            $dataInvoice = $this->request('get', 'factura/'.$nextReminder, $region, []);
291
292            if ($this->checkClientHasFreshdeskTask($dataInvoice['factura']['cod_cliente'], $region)) {
293                continue;
294            }
295
296            $cobrada = $dataInvoice['factura']['cobrada'];
297            $formaPago = $dataInvoice['factura']['forma_pago_factura'];
298
299            // Invoice already paied
300            // Invoice that the payment method is not "transferencia"
301            if (
302                $cobrada !== "NO"
303                || stripos((string) $formaPago, "tr") === false
304            ) {
305                continue;
306            }
307
308            $dataClient = $this->request('get', 'cliente/'.$dataInvoice['factura']['cod_cliente'], $region, []);
309
310            $invoice_number = $dataInvoice['factura']['n_factura'] ?? null;
311            $client_name = $dataClient['cliente']['empresa'] ?? null;
312            $client_email = $dataClient['cliente']['email'] ?? $dataClient['cliente']['email_facturacion'] ?? null;
313            $cif = $dataClient['cliente']['cliente_cif'] ?? null;
314            $issued_date = $dataInvoice['factura']['fecha_emision'] ?? null;
315            $expiration_date = $this->getVencimientosFormateados($dataInvoice);
316            $amount = $dataInvoice['factura']['importe_total_factura'] ?? null;
317            $collection_date = null;
318            $document = $dataInvoice['factura']['documento'] ?? null;
319
320            if (config("app.env") === "production") {
321                $resultSend = $this->sendInvoice($invoice_number, $client_name, $client_email, $issued_date, $expiration_date, $document, $amount, 2, $region);
322
323                if (! $resultSend['success']) {
324                    continue;
325                }
326            }
327
328            TblInvoiceReminders::create([
329                "invoice_number" => $invoice_number,
330                "client_name" => $client_name,
331                "client_email" => $client_email,
332                "cif" => $cif,
333                "issued_date" => $issued_date,
334                "expiration_date" => $expiration_date,
335                "amount" => $amount,
336                "collection_date" => $collection_date,
337                "region" => $region,
338                "reminder_type" => 2
339            ]);
340            }
341    }
342
343    private function loopNextRemindersClients($region, string $next10days, string $lastWeek): void{
344        $diaNext10 = date("j", strtotime($next10days));
345        $diaLastWeek = date("j", strtotime($lastWeek));
346
347        $nextWeekInvoices = TblInvoicesNextReminders::where('payment_day', $diaNext10)->get()->pluck('invoice_number');
348        $lastWeekInvoices = TblInvoicesNextReminders::where('payment_day', $diaLastWeek)->get()->pluck('invoice_number');
349
350        // type 1
351        foreach ($nextWeekInvoices as $reminder) {
352            if (! $reminder) {
353                continue;
354            }
355            $dataInvoice = $this->request('get', 'factura/'.$reminder, $region, []);
356            if ($this->checkClientHasFreshdeskTask($dataInvoice['factura']['cod_cliente'], $region)) {
357                continue;
358            }
359
360            $cobrada = $dataInvoice['factura']['cobrada'];
361            $formaPago = $dataInvoice['factura']['forma_pago_factura'];
362
363            // Invoice already paied
364            // Invoice that the payment method is not "transferencia"
365            if (
366                $cobrada !== "NO"
367                || stripos((string) $formaPago, "tr") === false
368            ) {
369                continue;
370            }
371
372            $dataClient = $this->request('get', 'cliente/'.$dataInvoice['factura']['cod_cliente'], $region, []);
373
374            $invoice_number = $dataInvoice['factura']['n_factura'] ?? null;
375            $client_name = $dataClient['cliente']['empresa'] ?? null;
376            $client_email = $dataClient['cliente']['email'] ?? $dataClient['cliente']['email_facturacion'] ?? null;
377            $cif = $dataClient['cliente']['cliente_cif'] ?? null;
378            $issued_date = $dataInvoice['factura']['fecha_emision'] ?? null;
379            $expiration_date = $this->getVencimientosFormateados($dataInvoice);
380            $amount = $dataInvoice['factura']['importe_total_factura'] ?? null;
381            $collection_date = null;
382            $document = $dataInvoice['factura']['documento'] ?? null;
383
384            if (config("app.env") === "production") {
385                $resultSend = $this->sendInvoice($invoice_number, $client_name, $client_email, $issued_date, $expiration_date, $document, $amount, 1, $region);
386
387                if (! $resultSend['success']) {
388                    continue;
389                }
390            }
391
392            TblInvoiceReminders::create([
393                "invoice_number" => $invoice_number,
394                "client_name" => $client_name,
395                "client_email" => $client_email,
396                "cif" => $cif,
397                "issued_date" => $issued_date,
398                "expiration_date" => $expiration_date,
399                "amount" => $amount,
400                "collection_date" => $collection_date,
401                "region" => $region,
402                "reminder_type" => 1
403            ]);
404        }
405
406        // type 3
407        foreach ($lastWeekInvoices as $reminder) {
408            if (! $reminder) {
409                continue;
410            }
411            $dataInvoice = $this->request('get', 'factura/'.$reminder, $region, []);
412            if ($this->checkClientHasFreshdeskTask($dataInvoice['factura']['cod_cliente'], $region)) {
413                continue;
414            }
415
416            $cobrada = $dataInvoice['factura']['cobrada'];
417            $formaPago = $dataInvoice['factura']['forma_pago_factura'];
418
419            // Invoice already paied
420            // Invoice that the payment method is not "transferencia"
421            if (
422                $cobrada !== "NO"
423                || stripos((string) $formaPago, "tr") === false
424            ) {
425                continue;
426            }
427
428            $dataClient = $this->request('get', 'cliente/'.$dataInvoice['factura']['cod_cliente'], $region, []);
429
430            $invoice_number = $dataInvoice['factura']['n_factura'] ?? null;
431            $client_name = $dataClient['cliente']['empresa'] ?? null;
432            $client_email = $dataClient['cliente']['email'] ?? $dataClient['cliente']['email_facturacion'] ?? null;
433            $cif = $dataClient['cliente']['cliente_cif'] ?? null;
434            $issued_date = $dataInvoice['factura']['fecha_emision'] ?? null;
435            $expiration_date = $this->getVencimientosFormateados($dataInvoice);
436            $amount = $dataInvoice['factura']['importe_total_factura'] ?? null;
437            $collection_date = null;
438            $document = $dataInvoice['factura']['documento'] ?? null;
439
440            if (config("app.env") === "production") {
441                $resultSend = $this->sendInvoice($invoice_number, $client_name, $client_email, $issued_date, $expiration_date, $document, $amount,  3, $region);
442
443                if (! $resultSend['success']) {
444                    continue;
445                }
446            }
447
448            TblInvoiceReminders::create([
449                "invoice_number" => $invoice_number,
450                "client_name" => $client_name,
451                "client_email" => $client_email,
452                "cif" => $cif,
453                "issued_date" => $issued_date,
454                "expiration_date" => $expiration_date,
455                "amount" => $amount,
456                "collection_date" => $collection_date,
457                "region" => $region,
458                "reminder_type" => 3
459            ]);
460        }
461
462    }
463
464    public function sendInvoice($invoice_number, $client_name, $client_email, $issued_date, $expiration_date, $document, $amount, $reminder_type, $region, $table = null, $month = null): array {
465        if (
466            empty($invoice_number) ||
467            empty($client_name) ||
468            empty($client_email) ||
469            empty($issued_date) ||
470            empty($expiration_date) ||
471            empty($document) ||
472            empty($reminder_type)
473        ) {
474            Log::channel('g3w_invoices_not_send')->error("$invoice_number => {
475            'client_name': $client_name,
476            'client_email': $client_email,
477            'issued_date': $issued_date,
478            'expiration_date': $expiration_date,
479            'reminder_type': $reminder_type
480        }");
481
482            return ['success' => false, 'message' => 'Campos obligatorios faltantes'];
483        }
484
485        try {
486            $emailTemplate = TblInvoiceRemindersEmailTemplate::where('type', $reminder_type)->first();
487
488            if (! $emailTemplate) {
489                throw new \Exception('No se encontró la plantilla de correo para este tipo de recordatorio');
490            }
491
492            $clientEmails = preg_split('/\s*[,;\-\s]\s*/', (string) $client_email, -1, PREG_SPLIT_NO_EMPTY);
493            $clientEmails = array_map(trim(...), $clientEmails);
494            $validEmails = [];
495
496            foreach ($clientEmails as $email) {
497                if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
498                    $validEmails[] = $email;
499                } else {
500                    Log::channel('g3w_invoices_not_send')->warning("Email inválido omitido: $email para factura $invoice_number");
501                }
502            }
503
504            if (empty($validEmails)) {
505                Log::channel('g3w_invoices_not_send')->error("No hay emails válidos para enviar factura $invoice_number");
506
507                return ['success' => false, 'message' => 'No hay direcciones de correo válidas'];
508            }
509
510            $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
511            $successCount = 0;
512            $failCount = 0;
513
514            foreach ($validEmails as $toEmail) {
515                try {
516                    $email = new \SendGrid\Mail\Mail;
517
518                    $fromEmail = 'recordatorio.factura@fire.es';
519                    $fromName = 'Recordatorio Facturas Grupo Fire';
520                    $email->setFrom($fromEmail, $fromName);
521                    $email->setReplyTo('recordatorio.facturas@fire.es', 'Recordatorio Facturas Grupo Fire');
522
523                    if ($reminder_type != 4) {
524                        $subject = str_replace(
525                            // ['{{invoice_number}}', '{{expiration_date}}', '{{amount}}'],
526                            ['{{invoice_number}}', '{{expiration_date}}'],
527                            [$invoice_number, $expiration_date, $amount],
528                            $emailTemplate->subject
529                        );
530                    } else {
531                        $subject = str_replace(
532                            // ['{{invoice_number}}', '{{expiration_date}}', '{{amount}}'],
533                            ['{{expiration_date}}'],
534                            [$month],
535                            $emailTemplate->subject
536                        );
537                    }
538
539                    $baseUrl = "http://aiwf.fire.es/sepa/form/";
540    
541                    $params = [
542                        'nombre_deudor' => $client_name,
543                        // 'cif_deudor' => $datos['cif'],
544                        // 'direccion_deudor' => urlencode($datos['direccion_deudor']),
545                        // 'codigo_postal' => urlencode($datos['codigo_postal']),
546                        // 'provincia_pais' => urlencode($datos['provincia_pais']),
547                        // 'iban' => $datos['iban'],
548                        // 'swift' => $datos['swift'],
549                        // 'nombre_apoderado' => urlencode($datos['nombre_apoderado']),
550                        // 'nif_apoderado' => $datos['nif_apoderado'],
551                        'fecha' => $issued_date,
552                        'email_contacto' => $client_email,
553                        'region' => $region
554                    ];
555                    
556                    $url = $baseUrl . '?' . http_build_query($params);
557                    
558
559                    Carbon::setLocale('es');
560
561                    if ($reminder_type < 4) {
562                        $body = str_replace(
563                            ['{{invoice_number}}', '{{client_name}}', '{{issued_date}}', '{{expiration_date}}', '{{amount}}', '{{url}}'],
564                            [
565                                $invoice_number,
566                                $client_name,
567                                Carbon::createFromFormat('Y-m-d', $issued_date)->isoFormat('D [de] MMMM [de] YYYY'),
568                                $expiration_date,
569                                $amount,
570                                $url,
571                            ],
572                            $emailTemplate->html_content
573                        );
574                    } else {
575                        $body = str_replace(
576                            ['{{invoice_number}}', '{{client_name}}', '{{issued_date}}', '{{expiration_date}}', '{{amount}}', '{{table}}'],
577                            [
578                                $invoice_number,
579                                $client_name,
580                                Carbon::createFromFormat('Y-m-d', $issued_date)->isoFormat('D [de] MMMM [de] YYYY'),
581                                $expiration_date,
582                                $amount,
583                                $table,
584                            ],
585                            $emailTemplate->html_content
586                        );
587                    }
588
589                    $email->setSubject($subject);
590                    $email->addContent('text/html', $body);
591                    $email->addTo($toEmail, $client_name);
592
593                    if (is_array($document)) {
594                        foreach ($document as $doc) {
595                            if (!base64_decode((string) $doc["content"], true)) {
596                                throw new \Exception('El documento no es un base64 válido');
597                            }
598
599                            $attachment = new \SendGrid\Mail\Attachment;
600                            $attachment->setContent($doc['content']);
601                            $attachment->setType('application/pdf');
602                            $attachment->setFilename($doc['filename']);
603                            $attachment->setDisposition('attachment');
604                            $attachment->setContentId('factura_'.uniqid());
605                            $email->addAttachment($attachment);
606
607                        }
608                    }
609
610                    if (!is_array($document)) {
611                        if (!base64_decode((string) $document, true)) {
612                            throw new \Exception('El documento no es un base64 válido');
613                        }
614
615                        $attachment = new \SendGrid\Mail\Attachment;
616                        $attachment->setContent($document);
617                        $attachment->setType('application/pdf');
618                        $attachment->setFilename("Factura_{$invoice_number}.pdf");
619                        $attachment->setDisposition('attachment');
620                        $attachment->setContentId('factura_'.uniqid());
621                        $email->addAttachment($attachment);
622                    }
623
624                    $response = $sendgrid->send($email);
625
626                    if ($response->statusCode() == 202) {
627                        $successCount++;
628                        Log::channel('g3w_invoices_sent')->info("Factura $invoice_number enviada exitosamente a: $toEmail");
629                    } else {
630                        $failCount++;
631                        Log::channel('g3w_invoices_not_send')->error("Error enviando a $toEmail".$response->body());
632                    }
633
634                } catch (\Exception $e) {
635                    $failCount++;
636                    Log::channel('g3w_invoices_not_send')->error("Error enviando a $toEmail".$e->getMessage());
637
638                    continue;
639                }
640            }
641
642            if ($successCount > 0) {
643                return [
644                    'success' => true,
645                    'message' => "Enviados: $successCount, Fallidos: $failCount",
646                    'sent_count' => $successCount,
647                    'failed_count' => $failCount,
648                ];
649            } else {
650                return [
651                    'success' => false,
652                    'message' => 'Todos los envíos fallaron',
653                    'sent_count' => $successCount,
654                    'failed_count' => $failCount,
655                ];
656            }
657
658        } catch (\Exception $e) {
659            Log::channel('g3w_invoices_not_send')->error("Error general enviando factura $invoice_number".$e->getMessage());
660
661            return ['success' => false, 'message' => $e->getMessage()];
662        }
663    }
664
665    public function getAllInvoices($region = 'Cataluña')
666    {
667        if ($region === 'All') {
668            $invoices = TblInvoiceReminders::orderBy('id', 'desc')
669                ->paginate(50);
670        } else {
671            $invoices = TblInvoiceReminders::where('region', $region)
672                ->orderBy('id', 'desc')
673                ->paginate(50);
674        }
675
676        return response()->json([
677            'invoices' => $invoices->items(),
678            'pagination' => [
679                'total' => $invoices->total(),
680                'current_page' => $invoices->currentPage(),
681                'per_page' => $invoices->perPage(),
682                'last_page' => $invoices->lastPage(),
683            ],
684        ]);
685    }
686
687    public function getAllInvoicesExceptions()
688    {
689        $invoices = TblInvoicesExceptions::orderBy('id', 'desc')->paginate(50);
690
691        return response()->json([
692            'invoices' => $invoices->items(),
693            'pagination' => [
694                'total' => $invoices->total(),
695                'current_page' => $invoices->currentPage(),
696                'per_page' => $invoices->perPage(),
697                'last_page' => $invoices->lastPage(),
698            ],
699        ]);
700    }
701
702    public function sendCyCInvoices(?string $region): array{
703        try{
704            if(!$region){
705                throw new Exception("No region provided");
706            }
707
708            $fromDay = 45;
709            $toDay = 35;
710            $today = Carbon::createFromDate(date('Y'), date('m'), date('d'));
711            $todayFormatted = Carbon::now()->format('Y-m-d');
712
713            $documentName = $todayFormatted.'.csv';
714
715            $filePath = storage_path('app/public/uploads/CyC/'.$todayFormatted.'/'.$region.'/'.$documentName);
716
717            if (! file_exists(dirname($filePath))) {
718                mkdir(dirname($filePath), 0777, true);
719            }
720
721            $file = fopen($filePath, 'w');
722
723            fputcsv($file, [
724                'Service',
725                'Language',
726                'Cyc Poliza',
727                'SP Tax Idenfication Number',
728                'SP Country',
729                'BP TaxIdentificationNumber',
730                'SP Corporate Name',
731                'BP Country',
732                'Invoice Number',
733                'Invoice Issue Date',
734                'PD Installment Due Date',
735                'PD Payment Means',
736                'Total Amount',
737                'Taxable Base',
738                'Tax Rate',
739                'Tax Amount'
740            ], ";", escape: '\\');
741
742            while ($fromDay > $toDay) {
743                $date = $today->copy()->subDays($fromDay)->format('Y-m-d');
744                $invoices = $this->request('get', 'factura/vence/'.$date, $region, []);
745                foreach ($invoices['facturas'] as $invoice) {
746                    $invoiceData = $this->request('get', 'factura/'.$invoice['ID'], $region, []);
747                    $invoiceData = $invoiceData['factura'];
748
749                    $dataClient = $this->request('get', 'cliente/'.$invoiceData['cod_cliente'], $region, []);
750                    $invoiceCobrada = $invoiceData['cobrada'];
751                    $invoiceDocument = $invoiceData['documento'];
752                    $invoiceNumber = $invoiceData['n_factura'];
753
754                    if (
755                        ! $invoiceDocument ||
756                        $invoiceCobrada !== 'NO') {
757                        continue;
758                    }
759
760                    if($region === "Cataluña" && stripos((string) $invoiceNumber, "R") === false){
761                        continue;
762                    }
763
764                    fputcsv($file, [
765                        'grabacionFacturas',
766                        'ESP',
767                        '156547',
768                        'B67795088',
769                        'ESP',
770                        $dataClient['cliente']['cliente_cif'],
771                        $dataClient['cliente']['empresa'],
772                        'ESP',
773                        $invoiceNumber,
774                        $invoiceData["fecha_creacion"],
775                        $invoiceData["vencimientos"][0]["fecha_vencimiento"],
776                        $invoiceData["forma_pago_factura"],
777                        $invoiceData["importe_total_factura"],
778                        $invoiceData["base_imponible_factura"],
779                        $invoiceData["iva_factura"]*100,
780                        $invoiceData["importe_iva_factura"]
781                    ],";", escape: '\\');
782
783                }
784                $fromDay--;
785            }
786
787            fclose($file);
788
789            return ['success' => true];
790        } catch (\Exception $e) {
791            Log::channel('g3w_invoices')->error($e->getMessage());
792
793            return ['success' => false, 'error' => $e->getMessage()];
794        }
795    }
796    public function setAllMonthAdministratorsInvoices($region): array{
797        if(!$region){
798            return ['success'=> false,'error'=> 'No region provided'];
799        }
800
801        try {
802            $now = Carbon::now();
803            $month = $now->format('m');
804        } catch (\Exception $e) {
805            Log::channel('g3w_invoices')->error('Formato de fecha de mes no válido (esperado YYYY-mm)');
806
807            return ['success' => false, 'error' => 'Formato de fecha de mes no válido (esperado YYYY-mm)'];
808        }
809
810        $invoices = $this->request('get', 'factura/vencemesadministrador/'.$month, $region, []);
811
812        foreach ($invoices['facturas'] as $invoice) {
813            $invoiceData = $this->request('get', 'factura/'.$invoice['ID'], $region, []);
814            $invoiceData = $invoiceData['factura'];
815
816            // Continue if region is Comunidad Valenciana and the invoice starts with M
817            if(
818                $region == "Comunidad Valenciana"
819                && str_starts_with((string) $invoice["ID"], 'M')
820            ){
821                continue;
822            }
823
824            $cobrada = $invoiceData['cobrada'];
825            $formaPago = $invoiceData['forma_pago_factura'];
826            // Invoice already paied
827            // Invoice that the payment method is not "transferencia"
828            if (
829                $cobrada !== "NO"
830                || stripos((string) $formaPago, "tr") === false
831            ) {
832                continue;
833            }
834
835            $dataClient = [];
836            if ($invoice['cod_administrador']) {
837                $dataClient = $this->request('get', 'cliente/'.$invoice['cod_administrador'], $region, []);
838                $dataClient = $dataClient['cliente'];
839            }
840
841            if ($invoice['cod_cliente']) {
842                $dataClientAdministrado = $this->request('get', 'cliente/'.$invoice['cod_cliente'], $region, []);
843                $dataClientAdministrado = $dataClientAdministrado['cliente'];
844                if ($dataClientAdministrado['tipo_cliente'] === 'Grandes Cuentas') {
845                    continue;
846                }
847            }
848
849            /*if (
850                $dataClient["tipo_cliente"] !== "Administrador"
851                ){
852                continue;
853            }*/
854
855            $codService = $invoiceData['lineas'][0]['cod_servicio'];
856            $dataService = $this->request('get', 'servicio/'.$codService, $region, []);
857            $dataService = $dataService['servicio'];
858
859            TblInvoiceAdministrators::create([
860                'invoice_number' => $invoiceData['n_factura'],
861                'region' => $region,
862                'name' => $dataClient['empresa'],
863                'CIF' => $dataClient['cliente_cif'],
864                'email' => $dataClient['email'],
865                'service_name' => $dataService['nombre_servicio'],
866                'service_addres' => $dataService['direccion'],
867                'send_date' => Carbon::now('Europe/Madrid')->toDateString(),
868                'expiration_date' => Carbon::parse($invoice["fecha_vencimiento"])->toDateString(),
869                'amount' => $invoiceData["importe_total_factura"],
870                "cod_administrador"=> $invoice["cod_administrador"],
871                "cod_cliente"=> $invoice["cod_cliente"]
872            ]);
873        }
874
875        return [
876            'success' => true,
877            'message' => "Proceso completado para el mes $month en la región $region.",
878        ];
879    }
880
881    /**
882     * @return 'No region provided'[]|'Processing complete'[]|bool[]
883     */
884    public function sendAdministratorsInvoices($region): array{
885        if (!$region) {
886            return ['success' => false, 'error' => 'No region provided'];
887        }
888
889        $month = [
890            1 => 'Enero',
891            2 => 'Febrero',
892            3 => 'Marzo',
893            4 => 'Abril',
894            5 => 'Mayo',
895            6 => 'Junio',
896            7 => 'Julio',
897            8 => 'Agosto',
898            9 => 'Septiembre',
899            10 => 'Octubre',
900            11 => 'Noviembre',
901            12 => 'Diciembre',
902        ];
903
904        $startOfMonth = Carbon::now()->startOfMonth()->toDateString();
905        $endOfMonth = Carbon::now()->endOfMonth()->toDateString();
906
907        $currentMonthInvoices = TblInvoiceAdministrators::where('region', $region)
908            ->where('paid', 0)
909            ->whereBetween('expiration_date', [$startOfMonth, $endOfMonth])
910            ->orderBy('expiration_date', 'asc')
911            ->get();
912
913        $invoicesGroupedByAdministrator = $currentMonthInvoices->groupBy('cod_administrador');
914
915        foreach ($invoicesGroupedByAdministrator as $administratorCode => $administratorInvoices) {
916            $table = "<table style='border-collapse: collapse; width: 100%; font-family: Arial, sans-serif;'>
917            <tr>
918                <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Número Factura</th>
919                <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Nombre cliente de servicio</th>
920                <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Dirección del servicio</th>
921                <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Fecha de emisión</th>
922                <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Fecha de vencimiento</th>
923                <th style='border: 1px solid #999; padding: 8px; background-color: #f2f2f2; text-align: left;'>Importe</th>
924            </tr>";
925
926            $documentsBase64 = [];
927
928            foreach ($administratorInvoices as $invoice) {
929                if ($this->checkClientHasFreshdeskTask($invoice->cod_cliente, $invoice->region)) {
930                    continue;
931                }
932
933                $invoiceNumber = $invoice->invoice_number;
934                $table .= "<tr>
935                    <td class='invoice_number' style='border: 1px solid #999; padding: 8px;'>".$invoiceNumber."</td>
936                    <td class='invoice_service_name' style='border: 1px solid #999; padding: 8px;'>".$invoice->service_name."</td>
937                    <td class='invoice_service_addres' style='border: 1px solid #999; padding: 8px;'>".$invoice->service_addres."</td>
938                    <td class='invoice_send_date' style='border: 1px solid #999; padding: 8px;'>".$invoice->send_date."</td>
939                    <td class='invoice_expiration_date' style='border: 1px solid #999; padding: 8px;'>".$invoice->expiration_date."</td>
940                    <td class='invoice_amount' style='border: 1px solid #999; padding: 8px;'>".$invoice->amount.' €</td>
941                </tr>';
942
943                $invoice = $this->request('get', 'factura/'.$invoiceNumber, $region, []);
944                $invoiceData = $invoice['factura'];
945
946                $documentsBase64[] = [
947                    'content' => $invoiceData['documento'],
948                    'filename' => "Factura_{$invoiceNumber}.pdf",
949                ];
950            }
951
952            $table .= '</table>';
953
954            if (empty($documentsBase64)) {
955                continue;
956            }
957
958            $monthText = $month[Carbon::now()->format('n')];
959
960            $invoiceSendData = $this->sendInvoice(
961                $administratorInvoices->first()->invoice_number,
962                $administratorInvoices->first()->name,
963                $administratorInvoices->first()->email,
964                $administratorInvoices->first()->send_date,
965                $administratorInvoices->first()->expiration_date,
966                $documentsBase64,
967                $administratorInvoices->first()->amount,
968                4,
969                $region,
970                $table,
971                $monthText.' de '.Carbon::now()->format('Y')
972            );
973            
974            TblInvoiceReminders::create([
975                "invoice_number" => $administratorInvoices->first()->invoice_number,
976                "client_name" => $administratorInvoices->first()->name,
977                "client_email" => $administratorInvoices->first()->email,
978                "cif" => "Administradores",
979                "issued_date" => date("Y-m-d"),
980                "expiration_date" => $administratorInvoices->first()->expiration_date,
981                "amount" => $administratorInvoices->first()->amount,
982                "collection_date" => null,
983                "region" => $region,
984                "reminder_type" => 4
985            ]);
986
987            TblInvoiceReminders::create([
988                'invoice_number' => $administratorInvoices->first()->invoice_number,
989                'client_name' => $administratorInvoices->first()->name,
990                'client_email' => $administratorInvoices->first()->email,
991                'cif' => 'Administradores',
992                'issued_date' => date('Y-m-d'),
993                'expiration_date' => $administratorInvoices->first()->expiration_date,
994                'amount' => $administratorInvoices->first()->amount,
995                'collection_date' => null,
996                'region' => $region,
997                'reminder_type' => 4,
998            ]);
999
1000        }
1001
1002        return ['success' => true, 'message' => 'Processing complete'];
1003    }
1004
1005    private function checkClientHasFreshdeskTask($client, $region): false {
1006        try {
1007            $response = Http::withBasicAuth('titan_auto', 'n71Mhh8i7FO_h2fF8e4')
1008                ->post('https://aiwf.ibvgroup.com/webhook/client-lookup', [
1009                    'client_id' => $client,
1010                    'region' => $region,
1011                ]);
1012
1013            if ($response->successful()) {
1014                return $response->json('found', false);
1015            }
1016
1017            return false;
1018
1019        } catch (\Exception $e) {
1020            Log::error('Error on checkClientHasFreshdeskTask: '.$e->getMessage());
1021
1022            return false;
1023        }
1024    }
1025
1026    public function checkClientHasFreshdeskTaskAll(): array|false {
1027        try {
1028            $clients = TblInvoiceAdministrators::all();
1029            $codClients = [];
1030            foreach ($clients as $client) {
1031                $response = Http::withBasicAuth('titan_auto', 'n71Mhh8i7FO_h2fF8e4')
1032                    ->post('https://aiwf.ibvgroup.com/webhook/client-lookup', [
1033                        'client_id' => $client->cod_cliente,
1034                        'region' => $client->region,
1035                    ]);
1036                $data = $response->json();
1037
1038                if (isset($data['found']) && $data['found'] === true) {
1039                    $codClients[] = "{$client->cod_cliente} en {$client->region}";
1040                }
1041            }
1042
1043            return $codClients;
1044
1045        } catch (\Exception $e) {
1046            Log::error('Error on checkClientHasFreshdeskTask: '.$e->getMessage());
1047
1048            return false;
1049        }
1050    }
1051
1052    public function sendCallCenterInvoices()
1053    {
1054        $spreadsheetId = '1JxCICtqPtPrE8B_0nVJZYwIZ67Nv5gPYr9rW73Pz1_Q';
1055        $sheetName = 'Hoja 1';
1056
1057        try {
1058            $googleSheetsService = $this->getGoogleSheetsService();
1059
1060            $region = [
1061                'G3W Cat' => 'Cataluña',
1062                'G3W Mad' => 'Madrid',
1063                'G3W Val' => 'Comunidad Valenciana',
1064                // "G3W Alm"=>"Andalucia",
1065            ];
1066
1067            $fromEmail = [
1068                'G3W Cat' => 'fire@fire.es',
1069                'G3W Mad' => 'atencion@fire.es',
1070                'G3W Val' => 'facturacionval@fire.es',
1071                // "G3W Alm"=>"documentacion.almeria@fire.es",
1072            ];
1073
1074            // A: Entorno, B: ID Factura, C: Email, D: Enviado, E: Fecha Envío
1075            $range = $sheetName.'!A2:E';
1076            $response = $googleSheetsService->spreadsheets_values->get($spreadsheetId, $range);
1077            $rows = $response->getValues();
1078
1079            if (empty($rows)) {
1080                return;
1081            }
1082
1083            foreach ($rows as $index => $row) {
1084                $currentRowNumber = $index + 2;
1085
1086                $entorno = $row[0] ?? null;
1087                $idFactura = $row[1] ?? null;
1088                $emailToSend = $row[2] ?? null;
1089                $enviado = $row[3] ?? 'NO';
1090
1091                if ($idFactura && $emailToSend && strtoupper($enviado) !== 'SI') {
1092
1093                    try {
1094                        // --- AQUÍ LLAMAS A TU LÓGICA DE ENVÍO ---
1095                        if(
1096                            $region[$entorno] == "Comunidad Valenciana"
1097                            && str_starts_with((string) $idFactura, 'M')
1098                        ){
1099                            continue;
1100                        }
1101
1102                        $invoiceData = $this->request('get', 'factura/'.$idFactura, $region[$entorno], []);
1103                        $invoiceData = $invoiceData['factura'];
1104
1105                        $cobrada = $invoiceData['cobrada'];
1106                        $formaPago = $invoiceData['forma_pago_factura'];
1107                        // Invoice already paied
1108                        // Invoice that the payment method is not "transferencia"
1109                        if (
1110                            $cobrada !== "NO"
1111                            || stripos((string) $formaPago, "tr") === false
1112                        ) {
1113                            continue;
1114                        }
1115
1116                        $html = '<!doctypehtml><html lang=es><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>Recordatorio de Pago - Vencimiento en 1 semana</title><style>body{font-family:Arial,sans-serif;font-size:11;line-height:1.6;max-width:600px;margin:0 auto;padding:20px}@media only screen and (max-width:600px){body{padding:15px}}</style><p>Estimado cliente,</p><p>Le contactamos desde <strong>Grupo Fire</strong>, empresa responsable del mantenimiento de sus extintores, en relación con las <strong>facturas pendientes de pago</strong> que se detallan en los documentos adjuntos.</p><p>Tras revisar nuestra contabilidad, hemos comprobado que, a día de hoy, los importes correspondientes aún no han sido abonados. Por ello, le agradeceríamos que nos indicara si existe alguna incidencia o motivo que haya podido impedir el pago (por ejemplo: cambio de cuenta bancaria, domiciliación no autorizada, disconformidad con la factura, etc.).</p><p>En caso de no existir ninguna incidencia, le agradeceríamos proceder al abono de los importes pendientes a la mayor brevedad. Puede realizar el pago mediante ingreso o transferencia a la cuenta bancaria que figura en las facturas adjuntas.</p><p>Un cordial saludo,<br><img alt=""src="data:image/png;base64,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" style=width:"height: 45px;"></div>';
1117
1118                        $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
1119                        
1120                        $successCount = 0;
1121                        $failCount = 0;
1122
1123                        $email = new \SendGrid\Mail\Mail;
1124
1125                        // $fromEmail = 'recordatorio.factura@fire.es';
1126                        $fromName = 'Recordatorio Facturas Grupo Fire';
1127                        $email->setFrom($fromEmail[$entorno], $fromName);
1128                        $email->setReplyTo($fromEmail[$entorno], 'Recordatorio Facturas Grupo Fire');
1129                        $email->setSubject('Envío de factura '.$idFactura);
1130                        $email->addContent('text/html', $html);
1131                        $email->addTo($emailToSend);
1132
1133                        if (!base64_decode((string) $invoiceData["documento"], true)) {
1134                            throw new \Exception('El documento no es un base64 válido');
1135                        }
1136
1137                        $attachment = new \SendGrid\Mail\Attachment;
1138                        $attachment->setContent($invoiceData['documento']);
1139                        $attachment->setType('application/pdf');
1140                        $attachment->setFilename($idFactura);
1141                        $attachment->setDisposition('attachment');
1142                        $attachment->setContentId('factura_'.uniqid());
1143                        $email->addAttachment($attachment);
1144
1145                        $response = $sendgrid->send($email);
1146
1147                        if ($response->statusCode() != 202) {
1148                            throw new Exception('Envio fallido');
1149                        }
1150
1151                        // --- AQUÍ LLAMAS A TU LÓGICA DE ENVÍO ---
1152
1153                        // $updateRange = $sheetName . '!D' . $currentRowNumber . ':E' . $currentRowNumber;
1154                        $updateData = [[$entorno, $idFactura, $emailToSend, 'Si', date('Y-m-d H:i:s')]];
1155
1156                        $this->writeToGoogleSheet(
1157                            $googleSheetsService,
1158                            $spreadsheetId,
1159                            $sheetName,
1160                            $updateData,
1161                            $currentRowNumber
1162                        );
1163
1164                    } catch (\Exception $e) {
1165                        continue;
1166                    }
1167                }
1168            }
1169
1170            return ['success' => true, 'message' => 'Processing complete'];
1171
1172        } catch (\Exception $e) {
1173            Log::error('Error en sendCallCenterInvoices: '.$e->getMessage());
1174
1175            return response()->json(['success' => false, 'message' => $e->getMessage()]);
1176        }
1177    }
1178
1179    // Google sheets
1180    public function addToSheets($codCliente, $invoice, $region, $spreadsheetId = '15Lc9tJnHDOGp33V9RH86mXtIybJBAWWlW4fe7knhDZY')
1181    {
1182        $sheetName = 'Hoja 1';
1183        $googleSheetsService = null;
1184        $nextRow = 1;
1185
1186        try {
1187            $googleSheetsService = $this->getGoogleSheetsService();
1188            $range = $sheetName.'!A:A';
1189            $response = $googleSheetsService->spreadsheets_values->get($spreadsheetId, $range);
1190            $values = $response->getValues();
1191            $nextRow = $values ? count($values) + 1 : 1;
1192
1193        } catch (\Exception $e) {
1194            if (str_contains($e->getMessage(), 'Primera configuración requerida')) {
1195                return [
1196                    'success' => false,
1197                    'message' => $e->getMessage(),
1198                    'requires_auth' => true,
1199                ];
1200            }
1201
1202            return [
1203                'success' => false,
1204                'message' => 'Error de conexión con Google Sheets: '.$e->getMessage(),
1205            ];
1206        }
1207
1208        if ($googleSheetsService && $nextRow === 1) {
1209            $this->writeToGoogleSheet($googleSheetsService, $spreadsheetId, $sheetName, [
1210                ['ID Cliente', 'Número de Factura', 'Fecha de Compromiso de Pago', 'Fecha de Compromiso de Pago + 10 días', 'Region'],
1211            ], 1);
1212            $nextRow = 2;
1213        }
1214
1215        $fechaCompromiso = date('Y-m-d');
1216        $fechaCompromisoMas10 = date('Y-m-d', strtotime('+10 days'));
1217
1218        $data = [
1219            $codCliente,
1220            $invoice,
1221            $fechaCompromiso,
1222            $fechaCompromisoMas10,
1223            $region,
1224        ];
1225
1226        if ($googleSheetsService) {
1227            $this->writeToGoogleSheet($googleSheetsService, $spreadsheetId, $sheetName, [$data], $nextRow);
1228            $nextRow++;
1229        }
1230
1231    }
1232
1233    private function getGoogleSheetsService(): \Google\Service\Sheets
1234    {
1235
1236        $redirectUrl = "";
1237        if(config("app.env") === "production"){
1238            $redirectUrl = "https://fireservicetitan.com/api/google-sheets-callback";
1239        }
1240
1241        if(config("app.env") === "staging"){
1242            $redirectUrl = "https://stg.fireservicetitan.com/api/google-sheets-callback";
1243        }
1244
1245        if(config("app.env") === "local"){
1246            $redirectUrl = "http://localhost:8000/api/google-sheets-callback";
1247        }
1248
1249        $client = new \Google\Client;
1250        $client->setAuthConfig(storage_path('app/credentials.json'));
1251        $client->addScope(\Google\Service\Sheets::SPREADSHEETS);
1252        $client->setAccessType('offline');
1253        $client->setPrompt('select_account consent');
1254        $client->setRedirectUri($redirectUrl);
1255
1256        $tokenPath = storage_path('app/token.json');
1257
1258        if (file_exists($tokenPath)) {
1259            $accessToken = json_decode(file_get_contents($tokenPath), true);
1260            $client->setAccessToken($accessToken);
1261        }
1262
1263        if ($client->isAccessTokenExpired()) {
1264            if ($client->getRefreshToken()) {
1265                $newToken = $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
1266                file_put_contents($tokenPath, json_encode($newToken));
1267            } else {
1268                $authUrl = $client->createAuthUrl();
1269                throw new \Exception('Initial setup required. Visit this URL to authorize: '.$authUrl);
1270            }
1271        }
1272
1273        return new \Google\Service\Sheets($client);
1274    }
1275
1276    private function writeToGoogleSheet($service, $spreadsheetId, string $sheetName, array $data, int|float $rowNumber)
1277    {
1278        try {
1279            $range = $sheetName.'!A'.$rowNumber.':E'.$rowNumber;
1280
1281            $body = new \Google\Service\Sheets\ValueRange([
1282                'values' => $data,
1283            ]);
1284
1285            $params = [
1286                'valueInputOption' => 'USER_ENTERED',
1287            ];
1288
1289            $result = $service->spreadsheets_values->update(
1290                $spreadsheetId,
1291                $range,
1292                $body,
1293                $params
1294            );
1295
1296            return $result;
1297
1298        } catch (\Exception $e) {
1299            Log::error('❌ Error escribiendo en Google Sheets: '.$e->getMessage());
1300            throw $e;
1301        }
1302    }
1303
1304    public function handleGoogleAuthCallback(Request $request)
1305    {
1306        try {
1307            // Verificar que el código existe
1308            if (! $request->get('code')) {
1309                Log::error('❌ No code parameter found');
1310
1311                return response()->json([
1312                    'success' => false,
1313                    'message' => 'No authorization code provided',
1314                ], 400);
1315            }
1316
1317            $redirectUrl = '';
1318
1319            if(config("app.env") === "production"){
1320                $redirectUrl = "https://fireservicetitan.com/api/google-sheets-callback";
1321            }
1322
1323            if(config("app.env") === "staging"){
1324                $redirectUrl = "https://stg.fireservicetitan.com/api/google-sheets-callback";
1325            }
1326
1327            if(config("app.env") === "local"){
1328                $redirectUrl = "http://localhost:8000/api/google-sheets-callback";
1329            }
1330
1331            $client = new \Google\Client;
1332            $client->setAuthConfig(storage_path('app/credentials.json'));
1333            $client->addScope(\Google\Service\Sheets::SPREADSHEETS);
1334            $client->setRedirectUri($redirectUrl);
1335
1336            $token = $client->fetchAccessTokenWithAuthCode($request->get('code'));
1337
1338            file_put_contents(storage_path('app/token.json'), json_encode($token));
1339
1340            return response()->json([
1341                'success' => true,
1342                'message' => 'Google Sheets authentication complete! You can now run the invoicing function.',
1343            ]);
1344
1345        } catch (\Exception $e) {
1346            Log::error('Error on Google sheets callback: '.$e->getMessage());
1347            Log::error('Error details: '.$e->getTraceAsString());
1348
1349            return response()->json([
1350                'success' => false,
1351                'message' => 'Error: '.$e->getMessage(),
1352            ], 400);
1353        }
1354    }
1355
1356    private function getVencimientosFormateados(array $dataInvoice): ?string
1357    {
1358        $vencimientos = $dataInvoice['factura']['vencimientos'] ?? [];
1359
1360        if (count($vencimientos) === 0) {
1361            return null;
1362        }
1363
1364        $fechasFormateadas = array_map(fn(array $vencimiento) => Carbon::createFromFormat('Y-m-d', $vencimiento['fecha_vencimiento'])
1365            ->isoFormat('D [de] MMMM [de] YYYY'), $vencimientos);
1366
1367        return implode(' Ã³ ', $fechasFormateadas);
1368    }
1369
1370    public function listCreditDaysOffered($region): array
1371    {
1372        try {
1373
1374            ini_set('max_execution_time', 123456);
1375            $date = now()->subMonth()->toDateString();
1376
1377            $lastMonthClients = $this->request('get', 'cliente/nuevosconunafactura/'.$date, $region, []);
1378
1379            if (! $lastMonthClients) {
1380                throw new \Exception('Error al obtener los clientes o la lista está vacía');
1381            }
1382
1383            $tempData = [];
1384            $monthNames = [
1385                1 => 'Enero', 2 => 'Febrero', 3 => 'Marzo', 4 => 'Abril',
1386                5 => 'Mayo', 6 => 'Junio', 7 => 'Julio', 8 => 'Agosto',
1387                9 => 'Septiembre', 10 => 'Octubre', 11 => 'Noviembre', 12 => 'Diciembre',
1388            ];
1389
1390            foreach ($lastMonthClients as $client) {
1391                $id = $client['ID'];
1392                $clientInvoiceResponse = $this->request('get', 'factura/cliente/'.$id, $region, []);
1393                $invoices = $clientInvoiceResponse['facturas'] ?? [];
1394
1395                // 1. Filtro: Si tiene 2 o más facturas, saltamos (queremos 0 o 1)
1396                if (count($invoices) >= 2) {
1397                    continue;
1398                }
1399
1400                $clientData = $this->request('get', 'cliente/'.$id, $region, []);
1401                $clientData = $clientData['cliente'];
1402
1403                // 2. Inicializamos valores por defecto para clientes SIN factura
1404                $clientValue = 0;
1405                $creditDays = 0;
1406                $invoiceData = null;
1407
1408                // 3. Solo si tiene exactamente 1 factura, buscamos sus datos reales
1409                if (count($invoices) === 1) {
1410                    $invoiceResponse = $this->request('get', 'factura/'.$invoices[0]['numero'], $region, []);
1411                    $invoiceData = $invoiceResponse['factura'];
1412
1413                    $clientValue = $invoiceData['base_imponible_factura'] ?? 0;
1414
1415                    // Calculamos días de crédito solo si existen las fechas
1416                    if (! empty($invoiceData['fecha_creacion']) && ! empty($invoiceData['fecha_emision'])) {
1417                        $creation = Carbon::parse($invoiceData['fecha_creacion']);
1418                        $send = $invoiceData['vencimientos'][0]['fecha_vencimiento']
1419                                    ?? $invoiceData['fecha_emision'];
1420                        $creditDays = $creation->diffInDays($send);
1421                    }
1422                }
1423
1424                $date = ($clientData['fecha_alta'] === '0000-00-00' || empty($clientData['fecha_alta']))
1425                    ? Carbon::now()
1426                    : Carbon::parse($clientData['fecha_alta']);
1427
1428                $year = $date->year;
1429                $monthNum = $date->month;
1430                $nameMes = $monthNames[$monthNum];
1431                $weekNum = 'Semana '.$date->weekOfMonth;
1432
1433                // Estructura de agrupación (agregamos acumuladores)
1434                if (! isset($tempData[$year])) {
1435                    $tempData[$year] = [];
1436                }
1437                if (! isset($tempData[$year][$monthNum])) {
1438                    $tempData[$year][$monthNum] = [
1439                        'month' => $nameMes,
1440                        'value' => 0,
1441                        'credit_days' => 0,
1442                        'weeks' => [],
1443                    ];
1444                }
1445                if (! isset($tempData[$year][$monthNum]['weeks'][$weekNum])) {
1446                    $tempData[$year][$monthNum]['weeks'][$weekNum] = [
1447                        'created_at' => $weekNum,
1448                        'value' => 0,
1449                        'credit_days' => 0,
1450                        'clients' => [],
1451                    ];
1452                }
1453
1454                // Sumamos a los totales del mes/semana
1455                $tempData[$year][$monthNum]['value'] += $clientValue;
1456                $tempData[$year][$monthNum]['weeks'][$weekNum]['value'] += $clientValue;
1457
1458                $tempData[$year][$monthNum]['weeks'][$weekNum]['clients'][] = [
1459                    'cif' => $clientData['cliente_cif'],
1460                    'client_code' => $clientData['cod_cliente'],
1461                    'client_name' => $clientData['empresa'],
1462                    'value' => number_format($clientValue, 2, ',', '.').' €',
1463                    'credit_days' => $creditDays,
1464                    'payment_days' => $clientData['forma_pago'] ?? 'N/A',
1465                    'date' => $date->toDateString(),
1466                    'has_invoice' => count($invoices) === 1, // Flag útil para el frontend
1467                ];
1468            }
1469
1470            // Formateo final
1471            $finalData = [];
1472            foreach ($tempData as $year => $months) {
1473                $yearEntry = ['year' => $year, 'months' => []];
1474                foreach ($months as $m) {
1475                    $m['weeks'] = array_values($m['weeks']);
1476                    $m['value'] = number_format($m['value'], 2, ',', '.').' €';
1477                    $yearEntry['months'][] = $m;
1478                }
1479                $finalData[] = $yearEntry;
1480            }
1481
1482            return ['success' => true, 'data' => $finalData];
1483
1484        } catch (\Exception $e) {
1485            return ['success' => false, 'error' => $e->getMessage()];
1486        }
1487    }
1488}