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