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