2026-01-26 10:23:53 -06:00

643 lines
22 KiB
PHP

<?php
namespace App\Http\Controllers\Repuve;
use App\Http\Controllers\Controller;
use Barryvdh\DomPDF\Facade\Pdf;
use App\Models\Record;
use App\Models\Tag;
use Carbon\Carbon;
use Illuminate\Support\Facades\Storage;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Codedge\Fpdf\Fpdf\Fpdf;
use Illuminate\Http\Request;
class RecordController extends Controller
{
public function generatePdf($id)
{
$record = Record::with('vehicle.owner', 'user', 'module')->findOrFail($id);
$pdf = Pdf::loadView('pdfs.record', compact('record'))
->setPaper('a4', 'portrait')
->setOptions([
'defaultFont' => 'sans-serif',
'isHtml5ParserEnabled' => true,
'isRemoteEnabled' => true,
]);
return $pdf->stream('constancia-inscripcion-' . $id . '.pdf');
}
public function generatePdfVerification($id)
{
$record = Record::with('vehicle.owner', 'user')->findOrFail($id);
$pdf = Pdf::loadView('pdfs.verification', compact('record'))
->setPaper('a4', 'landscape')
->setOptions([
'defaultFont' => 'sans-serif',
'isHtml5ParserEnabled' => true,
'isRemoteEnabled' => true,
]);
return $pdf->stream('hoja-verificacion-' . $id . '.pdf');
}
public function generatePdfConstancia($id)
{
$record = Record::with('vehicle.owner.municipality', 'user')->findOrFail($id);
// Preparar datos con conversión UTF-8 a mayúsculas
$data = [
'niv' => $record->vehicle->niv,
'placa' => mb_strtoupper($record->vehicle->placa, 'UTF-8'),
'marca' => mb_strtoupper($record->vehicle->marca, 'UTF-8'),
'linea' => mb_strtoupper($record->vehicle->linea, 'UTF-8'),
'modelo' => $record->vehicle->modelo,
'full_name' => mb_strtoupper($record->vehicle->owner->full_name, 'UTF-8'),
'callep' => mb_strtoupper($record->vehicle->owner->callep ?? '', 'UTF-8'),
'num_ext' => $record->vehicle->owner->num_ext,
'municipality' => mb_strtoupper($record->vehicle->owner->municipality->name ?? '', 'UTF-8'),
'tipo_servicio' => mb_strtoupper($record->vehicle->tipo_servicio, 'UTF-8'),
];
$pdf = Pdf::loadView('pdfs.constancia', $data)
->setPaper('a4', 'landscape')
->setOptions([
'defaultFont' => 'DejaVu Sans',
'isHtml5ParserEnabled' => true,
'isRemoteEnabled' => true,
]);
return $pdf->stream('constancia-inscripcion' . $id . '.pdf');
}
/**
* Generar PDF con las imágenes
*/
public function generatePdfImages($id)
{
try {
// Obtener el record con sus archivos
$record = Record::with(['vehicle.owner', 'files'])->findOrFail($id);
// Validar que tenga archivos
if ($record->files->isEmpty()) {
return ApiResponse::NOT_FOUND->response([
'message' => 'El expediente no tiene imágenes adjuntas.',
'record_id' => $id,
]);
}
// Crear instancia de FPDF
$pdf = new Fpdf('P', 'mm', 'A4');
$pdf->SetAutoPageBreak(false);
$pdf->SetMargins(10, 10, 10);
$currentImage = 0;
foreach ($record->files as $file) {
$currentImage++;
// Buscar archivo en disk 'records'
$diskRecords = Storage::disk('records');
$fileContent = null;
if ($diskRecords->exists($file->path)) {
$fileContent = $diskRecords->get($file->path);
}
// Si no se encontró el archivo, continuar
if ($fileContent === null) {
continue;
}
// Agregar nueva página
$pdf->AddPage();
// Header con folio
$pdf->SetFillColor(44, 62, 80);
$pdf->Rect(0, 0, 210, 20, 'F');
$pdf->SetTextColor(255, 255, 255);
$pdf->SetFont('Arial', 'B', 14);
$pdf->SetXY(10, 7);
$pdf->Cell(0, 6, 'FOLIO: ' . $record->folio, 0, 1, 'L');
// Obtener ruta temporal del archivo
$tempPath = tempnam(sys_get_temp_dir(), 'pdf_img_');
file_put_contents($tempPath, $fileContent);
// Obtener dimensiones de la imagen
$imageInfo = getimagesize($tempPath);
if ($imageInfo !== false) {
list($originalWidth, $originalHeight) = $imageInfo;
$imageType = $imageInfo[2];
$availableWidth = 190; // 210mm - 20mm márgenes
$availableHeight = 247; // 297mm - 20mm header - 20mm footer - 10mm márgenes
// Calcular dimensiones manteniendo proporción
$ratio = min($availableWidth / $originalWidth, $availableHeight / $originalHeight);
$newWidth = $originalWidth * $ratio;
$newHeight = $originalHeight * $ratio;
// Centrar imagen
$x = (210 - $newWidth) / 2;
$y = 25 + (($availableHeight - $newHeight) / 2);
// Determinar tipo de imagen
$imageExtension = '';
switch ($imageType) {
case IMAGETYPE_JPEG:
$imageExtension = 'JPEG';
break;
case IMAGETYPE_JPEG:
$imageExtension = 'JPG';
break;
case IMAGETYPE_PNG:
$imageExtension = 'PNG';
break;
default:
// Si no es un formato soportado, continuar
unlink($tempPath);
continue 2;
}
// Insertar imagen
$pdf->Image($tempPath, $x, $y, $newWidth, $newHeight, $imageExtension);
}
// Limpiar archivo temporal
unlink($tempPath);
}
// Verificar que se agregaron páginas
if ($pdf->PageNo() == 0) {
return ApiResponse::NOT_FOUND->response([
'message' => 'No se pudieron procesar las imágenes del expediente.',
'record_id' => $id,
]);
}
// Generar PDF
$pdfContent = $pdf->Output('S');
return response($pdfContent, 200)
->header('Content-Type', 'application/pdf')
->header('Content-Disposition', 'inline; filename="expediente-imagenes-' . $record->folio . '.pdf"');
} catch (\Exception $e) {
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al generar el PDF de imágenes',
'error' => $e->getMessage(),
]);
}
}
public function generatePdfForm($id)
{
try {
$record = Record::with([
'vehicle',
'vehicle.owner',
'vehicle.tag',
])->findOrFail($id);
if (!$record->vehicle) {
return ApiResponse::NOT_FOUND->response([
'message' => 'El registro no tiene un vehículo asociado.',
'record_id' => $id,
]);
}
$vehicle = $record->vehicle;
$owner = $vehicle->owner;
$tag = $vehicle->tag;
$now = Carbon::now()->locale('es_MX');
$data = [
// Datos del vehículo
'marca' => strtoupper($vehicle->marca ?? ''),
'linea' => strtoupper($vehicle->linea ?? ''),
'modelo' => $vehicle->modelo ?? '',
'niv' => strtoupper($vehicle->niv ?? ''),
'numero_motor' => strtoupper($vehicle->numero_motor ?? ''),
'placa' => strtoupper($vehicle->placa ?? ''),
'folio' => $tag?->folio ?? $record->folio ?? '',
// Datos del propietario
'telefono' => $owner?->telefono ?? '',
// Fecha actual
'fecha' => $now->format('d'),
'mes' => ucfirst($now->translatedFormat('F')),
'anio' => $now->format('Y'),
'record_id' => $record->id,
'owner_name' => $owner?->full_name ?? '',
];
$pdf = Pdf::loadView('pdfs.form', $data)
->setPaper('a4', 'portrait')
->setOptions([
'defaultFont' => 'sans-serif',
'isHtml5ParserEnabled' => true,
'isRemoteEnabled' => true,
]);
return $pdf->stream('solicitud-sustitucion-' . time() . '.pdf');
} catch (\Exception $e) {
return ApiResponse::NOT_FOUND->response([
'message' => 'No se encontró el registro del expediente proporcionado.',
'record_id' => $id,
]);
} catch (\Exception $e) {
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al generar el PDF del formulario',
'error' => $e->getMessage(),
]);
}
}
public function pdfDamagedTag(Tag $tag)
{
try{
$tag->load('status');
if(!$tag->status){
return ApiResponse::NOT_FOUND->response([
'message' => 'El tag no tiene un estado asociado.',
'tag_id' => $tag->id,
]);
}
// Validar que el tag esté cancelado
if (!$tag->isDamaged()) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'Solo se puede generar PDF para tags dañados.',
'current_status' => $tag->status->name,
]);
}
// Obtener datos de cancelación
$cancellationData = $this->cancellationData($tag);
$pdf = Pdf::loadView('pdfs.tag', [
'cancellation' => $cancellationData,
])
->setPaper('a4', 'portrait')
->setOptions([
'defaultFont' => 'sans-serif',
'isHtml5ParserEnabled' => true,
'isRemoteEnabled' => true,
]);
return $pdf->stream('constancia_dañada_' . $tag->tag_number . '.pdf');
} catch (\Exception $e) {
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al generar el PDF.',
'error' => $e->getMessage(),
]);
}
}
public function pdfCancelledTag(Tag $tag)
{
try {
$tag->load('status');
if(!$tag->status){
return ApiResponse::NOT_FOUND->response([
'message' => 'El tag no tiene un estado asociado.',
'tag_id' => $tag->id,
]);
}
// Validar que el tag esté cancelado
if (!$tag->isCancelled()) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'Solo se puede generar PDF para tags cancelados.',
'current_status' => $tag->status->name,
]);
}
// Obtener datos de cancelación
$cancellationData = $this->cancellationData($tag);
$pdf = Pdf::loadView('pdfs.tag_cancelada', [
'cancellation' => $cancellationData,
])
->setPaper('a4', 'portrait')
->setOptions([
'defaultFont' => 'sans-serif',
'isHtml5ParserEnabled' => true,
'isRemoteEnabled' => true,
]);
return $pdf->stream('constancia_cancelada_' . $tag->tag_number . '.pdf');
} catch (\Exception $e) {
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al generar el PDF.',
'error' => $e->getMessage(),
]);
}
}
public function pdfSubstitutedTag($recordId)
{
try {
// Validar que el tag tenga una sustitución registrada
$record = Record::with([
'vehicle.vehicleTagLogs' => function ($query){
$query->where('action_type', 'sustitucion')
->whereNotNull('cancellation_at')
->latest();
}
])->findOrFail($recordId);
$oldTagLog = $record->vehicle->vehicleTagLogs->first();
if (!$oldTagLog) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'No se encontró una sustitución registrada para este expediente.',
'record' => $recordId,
]);
}
// Obtener datos de sustitución
$oldTag = Tag::with([
'vehicleTagLogs' => function($query){
$query->where('action_type', 'sustitucion')
->whereNotNull('cancellation_at')
->with(['cancellationReason', 'cancelledBy', 'vehicle'])
->latest();
}
])->findOrFail($oldTagLog->tag_id);
$hasSubstitution = $oldTag->vehicleTagLogs()
->where('action_type', 'sustitucion')
->whereNotNull('cancellation_at')
->exists();
if(!$hasSubstitution){
return ApiResponse::BAD_REQUEST->response([
'message' => 'El tag no tiene sustitución registrada.',
'tag' => $oldTag->folio,
]);
}
$substitutionData = $this->substitutionData($oldTag);
$pdf = Pdf::loadView('pdfs.tag_sustitution', [
'substitution' => $substitutionData,
])
->setPaper('a4', 'portrait')
->setOptions([
'defaultFont' => 'sans-serif',
'isHtml5ParserEnabled' => true,
'isRemoteEnabled' => true,
]);
return $pdf->stream('constancia_sustituida_' . $oldTag->folio . '.pdf');
} catch (\Exception $e) {
return ApiResponse::INTERNAL_ERROR->response([
'message' => 'Error al generar el PDF del tag sustituido.',
'error' => $e->getMessage(),
]);
}
}
private function cancellationData(Tag $tag)
{
$data = [
'fecha' => now()->format('d/m/Y'),
'folio' => $tag->folio ?? '',
'tag_number' => $tag->tag_number ?? '',
'placa' => '',
'niv' => '',
'motivo' => 'N/A',
'operador' => 'N/A',
'modulo' => 'No especificado',
'ubicacion' => 'No especificado',
];
// Cargar módulo del tag si existe
if ($tag->module_id) {
$tag->load('module');
if ($tag->module) {
$data['modulo'] = $tag->module->name ?? '';
$data['ubicacion'] = $tag->module->address ?? '';
}
}
// Intentar obtener datos del vehículo si existe
if ($tag->vehicle_id && $tag->vehicle) {
$data['id_chip'] = $tag->vehicle->id_chip ?? '';
$data['placa'] = $tag->vehicle->placa ?? '';
$data['niv'] = $tag->vehicle->niv ?? '';
} else {
// Si el tag no tiene vehicle_id, buscar en el último log
$lastLog = $tag->vehicleTagLogs()
->with('vehicle')
->whereNotNull('vehicle_id')
->latest('created_at')
->first();
if ($lastLog && $lastLog->vehicle) {
$data['id_chip'] = $lastLog->vehicle->id_chip ?? '';
$data['placa'] = $lastLog->vehicle->placa ?? '';
$data['niv'] = $lastLog->vehicle->niv ?? '';
}
}
// Buscar log de cancelación directa
$tagCancellationLog = $tag->cancellationLogs()
->with(['cancellationReason', 'cancelledBy'])
->latest()
->first();
if ($tagCancellationLog) {
$data['fecha'] = $tagCancellationLog->cancellation_at->format('d/m/Y');
$data['motivo'] = $tagCancellationLog->cancellationReason->name ?? 'No especificado';
$data['operador'] = $tagCancellationLog->cancelledBy->name ?? 'Sistema';
// Extraer datos adicionales de las observaciones
$this->extractAdditionalDataFromObservations($tagCancellationLog->cancellation_observations, $data);
// Cargar módulo del tag si existe, sino cargar módulo del usuario
if ($tag->module_id && $tag->module) {
$data['modulo'] = $tag->module->name;
$data['ubicacion'] = $tag->module->address;
} elseif ($tagCancellationLog->cancelledBy) {
$user = $tagCancellationLog->cancelledBy;
$this->loadUserModule($user, $data);
}
return $data;
}
// Buscar log de vehículo (tag asignado y luego cancelado)
$vehicleTagLog = $tag->vehicleTagLogs()
->where('action_type', 'cancelacion')
->with(['cancellationReason', 'cancelledBy', 'vehicle'])
->latest()
->first();
if ($vehicleTagLog) {
$data['motivo'] = $vehicleTagLog->cancellationReason->name ?? 'No especificado';
$data['operador'] = $vehicleTagLog->cancelledBy->name ?? 'Sistema';
// Cargar módulo del cual el usuario es responsable
if ($vehicleTagLog->cancelledBy) {
$user = $vehicleTagLog->cancelledBy;
$this->loadUserModule($user, $data);
}
if ($vehicleTagLog->vehicle) {
$data['id_chip'] = $vehicleTagLog->vehicle->id_chip ?? '';
$data['placa'] = $vehicleTagLog->vehicle->placa ?? '';
$data['niv'] = $vehicleTagLog->vehicle->niv ?? '';
}
}
return $data;
}
/**
* Extraer datos adicionales de las observaciones de cancelación
*/
private function extractAdditionalDataFromObservations($observations, &$data)
{
if (empty($observations)) {
return;
}
// Extraer ID CHIP
if (preg_match('/ID CHIP:\s*([^|]+)/', $observations, $matches)) {
$data['id_chip'] = trim($matches[1]);
}
// Extraer PLACA
if (preg_match('/PLACA:\s*([^|]+)/', $observations, $matches)) {
$data['placa'] = trim($matches[1]);
}
// Extraer VIN
if (preg_match('/VIN:\s*([^|]+)/', $observations, $matches)) {
$data['niv'] = trim($matches[1]);
}
}
private function substitutionData(Tag $tag)
{
$data = [
'fecha' => now()->format('d/m/Y'),
'folio' => $tag->folio ?? '',
'folio_sustituto' => '',
'id_chip' => '',
'placa' => '',
'niv' => '',
'motivo' => 'N/A',
'operador' => 'N/A',
'modulo' => '',
'ubicacion' => '',
];
// log de CANCELACIÓN del tag original
$oldTagLog = $tag->vehicleTagLogs()
->where('action_type', 'sustitucion')
->whereNotNull('cancellation_at')
->with(['cancellationReason', 'cancelledBy', 'vehicle'])
->latest()
->first();
if (!$oldTagLog) {
return $data; // No se encontró sustitución
}
// datos del motivo y operador
$data['fecha'] = $oldTagLog->cancellation_at->format('d/m/Y');
$data['motivo'] = $oldTagLog->cancellationReason->name ?? 'No especificado';
$data['operador'] = $oldTagLog->cancelledBy->name ?? 'Sistema';
// módulo del usuario
if ($oldTagLog->cancelledBy) {
$this->loadUserModule($oldTagLog->cancelledBy, $data);
}
// datos del vehículo
if ($oldTagLog->vehicle) {
$data['id_chip'] = $oldTagLog->vehicle->id_chip ?? '';
$data['placa'] = $oldTagLog->vehicle->placa ?? '';
$data['niv'] = $oldTagLog->vehicle->niv ?? '';
// tag NUEVO
$newTag = $oldTagLog->vehicle->tag;
$data['folio_sustituto'] = $newTag?->folio ?? '';
}
return $data;
}
/**
* Cargar módulo del usuario
*/
private function loadUserModule($user, &$data)
{
// Intentar cargar module
$user->load('module');
// Si no tiene module, usar responsibleModule
if (!$user->module) {
$user->load('responsibleModule');
if ($user->responsibleModule) {
$data['modulo'] = $user->responsibleModule->name;
$data['ubicacion'] = $user->responsibleModule->address;
}
} else {
$data['modulo'] = $user->module->name;
$data['ubicacion'] = $user->module->address;
}
}
public function errors(Request $request)
{
$request->validate([
'folio' => 'nullable|string',
'placa' => 'nullable|string',
'vin' => 'nullable|string',
]);
$records = Record::with(['vehicle.owner', 'vehicle.tag', 'files', 'user', 'error'])
->whereNotNull('api_response')
->whereRaw("JSON_EXTRACT(api_response, '$.has_error') = true")
->orderBy('id', 'ASC');
if ($request->filled('folio')) {
$records->where('folio', 'LIKE', '%' . $request->input('folio') . '%');
}
if ($request->filled('placa')) {
$records->whereHas('vehicle', function ($q) use ($request) {
$q->where('placa', 'LIKE', '%' . $request->input('placa') . '%');
});
}
if ($request->filled('vin')) {
$records->whereHas('vehicle', function ($q) use ($request) {
$q->where('niv', 'LIKE', '%' . $request->input('vin') . '%');
});
}
return ApiResponse::OK->response([
'message' => 'Expedientes con errores encontrados exitosamente',
'records' => $records->paginate(config('app.pagination')),
]);
}
}