with('supplier'); if (request()->filled('q')) { $query->where('name', 'like', '%' . request()->q . '%'); } $bills = $query->orderByDesc('created_at') ->paginate(config('app.pagination', 15)); return ApiResponse::OK->response(['bills' => $bills]); } /** * Store a newly created resource in storage. */ public function store(BillStoreRequest $request) { $data = $request->validated(); if ($request->hasFile('file')) { $data['file_path'] = $request->file('file')->store('bills', 'public'); } unset($data['file']); $bill = Bill::create($data); return ApiResponse::CREATED->response(['bill' => $bill]); } /** * Display the specified resource. */ public function show(Bill $bill) { return ApiResponse::OK->response(['bill' => $bill]); } /** * Update the specified resource in storage. */ public function update(BillUpdateRequest $request, Bill $bill) { $data = $request->validated(); if ($request->hasFile('file')) { if ($bill->file_path) { Storage::disk('public')->delete($bill->file_path); } $data['file_path'] = $request->file('file')->store('bills', 'public'); } unset($data['file']); $bill->update($data); return ApiResponse::OK->response(['bill' => $bill->fresh()]); } /** * Remove the specified resource from storage. */ public function destroy(Bill $bill) { if ($bill->file_path) { Storage::disk('public')->delete($bill->file_path); } $bill->delete(); return ApiResponse::OK->response(); } /** * Alterna el estado de pago de la factura. */ public function togglePaid(Bill $bill) { $bill->update(['paid' => !$bill->paid]); return ApiResponse::OK->response(['bill' => $bill->fresh()]); } /** * Exporta a Excel las facturas pendientes de pago. */ public function export() { $bills = Bill::with('supplier')->where('paid', false) ->orderBy('deadline') ->orderBy('created_at') ->get(); $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('Facturas por Pagar'); $sheet->getParent()->getDefaultStyle()->getFont()->setName('Arial')->setSize(10); $styleHeader = [ 'font' => ['bold' => true, 'size' => 10, 'color' => ['rgb' => 'FFFFFF']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '4472C4']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]], ]; $styleData = [ 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D0D0D0']]], 'alignment' => ['vertical' => Alignment::VERTICAL_CENTER], ]; // Título $sheet->mergeCells('A1:F1'); $sheet->setCellValue('A1', 'FACTURAS PENDIENTES DE PAGO'); $sheet->getStyle('A1')->applyFromArray([ 'font' => ['bold' => true, 'size' => 14], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], ]); $sheet->mergeCells('A2:F2'); $sheet->setCellValue('A2', 'Generado el ' . Carbon::now()->format('d/m/Y H:i')); $sheet->getStyle('A2')->applyFromArray([ 'font' => ['italic' => true, 'color' => ['rgb' => '666666']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], ]); // Encabezados $headers = ['A4' => '#', 'B4' => 'NOMBRE', 'C4' => 'PROVEEDOR', 'D4' => 'COSTO', 'E4' => 'FECHA LÍMITE', 'F4' => 'DÍAS RESTANTES']; foreach ($headers as $cell => $text) { $sheet->setCellValue($cell, $text); } $sheet->getStyle('A4:F4')->applyFromArray($styleHeader); $sheet->getRowDimension(4)->setRowHeight(22); // Datos $row = 5; $total = 0; foreach ($bills as $i => $bill) { $deadline = $bill->deadline ? Carbon::parse($bill->deadline) : null; // Positivo = días que faltan, negativo = días vencida $daysLeft = $deadline ? (int) Carbon::today()->diffInDays($deadline, false) : null; if ($daysLeft === null) { $daysLabel = '—'; } elseif ($daysLeft === 0) { $daysLabel = 'Hoy'; } elseif ($daysLeft > 0) { $daysLabel = "+{$daysLeft}"; } else { $daysLabel = (string) $daysLeft; // ya incluye el signo negativo } $sheet->setCellValue('A' . $row, $i + 1); $sheet->setCellValue('B' . $row, $bill->name); $sheet->setCellValue('C' . $row, $bill->supplier?->business_name ?? '—'); $sheet->setCellValue('D' . $row, (float) $bill->cost); $sheet->setCellValue('E' . $row, $deadline?->format('d/m/Y') ?? '—'); $sheet->setCellValue('F' . $row, $daysLabel); $sheet->getStyle('D' . $row)->getNumberFormat()->setFormatCode('$#,##0.00'); $sheet->getStyle('A' . $row . ':F' . $row)->applyFromArray($styleData); $sheet->getStyle('A' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle('D' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); $sheet->getStyle('E' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getStyle('F' . $row)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); // Verde si quedan días, rojo si está vencida if ($daysLeft !== null && $daysLeft > 0) { $sheet->getStyle('F' . $row)->getFont()->getColor()->setRGB('1A7A1A'); } elseif ($daysLeft !== null && $daysLeft < 0) { $sheet->getStyle('E' . $row . ':F' . $row)->getFont()->getColor()->setRGB('CC0000'); $sheet->getStyle('E' . $row . ':F' . $row)->getFont()->setBold(true); } elseif ($daysLeft === 0) { $sheet->getStyle('F' . $row)->getFont()->getColor()->setRGB('B45309'); $sheet->getStyle('F' . $row)->getFont()->setBold(true); } $total += (float) $bill->cost; $row++; } // Total $sheet->setCellValue('C' . $row, 'TOTAL PENDIENTE'); $sheet->setCellValue('D' . $row, $total); $sheet->getStyle('D' . $row)->getNumberFormat()->setFormatCode('$#,##0.00'); $sheet->getStyle('C' . $row . ':D' . $row)->applyFromArray([ 'font' => ['bold' => true, 'size' => 11], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => 'FFF2CC']], 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT], ]); // Anchos de columna $sheet->getColumnDimension('A')->setWidth(6); $sheet->getColumnDimension('B')->setWidth(36); $sheet->getColumnDimension('C')->setWidth(30); $sheet->getColumnDimension('D')->setWidth(18); $sheet->getColumnDimension('E')->setWidth(16); $sheet->getColumnDimension('F')->setWidth(16); // Generar archivo $fileName = 'Facturas_Pendientes_' . Carbon::now()->format('Ymd_His') . '.xlsx'; $filePath = storage_path('app/temp/' . $fileName); if (!file_exists(dirname($filePath))) mkdir(dirname($filePath), 0755, true); (new Xlsx($spreadsheet))->save($filePath); return response()->download($filePath, $fileName, [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ])->deleteFileAfterSend(true); } }