From 4784bbdb9e75c56fdbf82d3d1862672ee00a618d Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Wed, 4 Mar 2026 13:00:13 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20agrega=20soporte=20para=20generaci?= =?UTF-8?q?=C3=B3n=20de=20documentos=20Word=20y=20PDF,=20actualiza=20depen?= =?UTF-8?q?dencias=20y=20mejora=20la=20configuraci=C3=B3n=20de=20Docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docker/Dev/dockerfile | 2 + Docker/Prod/dockerfile | 2 + .../Controllers/Repuve/RecordController.php | 62 +++++-- .../Controllers/Repuve/UpdateController.php | 3 +- .../Requests/Repuve/VehicleStoreRequest.php | 2 +- .../Requests/Repuve/VehicleUpdateRequest.php | 2 +- composer.json | 1 + composer.lock | 162 +++++++++++++++++- database/seeders/ModuleSeeder.php | 10 -- database/seeders/RoleSeeder.php | 12 +- storage/app/.gitignore | 1 + storage/app/templates/.gitignore | 2 + 12 files changed, 220 insertions(+), 41 deletions(-) create mode 100755 storage/app/templates/.gitignore diff --git a/Docker/Dev/dockerfile b/Docker/Dev/dockerfile index 8bc2b49..182e0ac 100644 --- a/Docker/Dev/dockerfile +++ b/Docker/Dev/dockerfile @@ -15,6 +15,8 @@ RUN apk add --no-cache \ openssl \ bash \ mysql-client \ + libreoffice \ + ttf-dejavu \ && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip COPY --from=composer:latest /usr/bin/composer /usr/bin/composer diff --git a/Docker/Prod/dockerfile b/Docker/Prod/dockerfile index 1daeb80..b1e7786 100644 --- a/Docker/Prod/dockerfile +++ b/Docker/Prod/dockerfile @@ -12,6 +12,8 @@ RUN apk add --no-cache \ libzip-dev \ openssl \ bash \ + libreoffice \ + ttf-dejavu \ && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip COPY --from=composer:latest /usr/bin/composer /usr/bin/composer diff --git a/app/Http/Controllers/Repuve/RecordController.php b/app/Http/Controllers/Repuve/RecordController.php index 11e6c4c..5640b87 100644 --- a/app/Http/Controllers/Repuve/RecordController.php +++ b/app/Http/Controllers/Repuve/RecordController.php @@ -12,6 +12,8 @@ use Codedge\Fpdf\Fpdf\Fpdf; use Illuminate\Http\Request; use Illuminate\Routing\Controllers\HasMiddleware; +use PhpOffice\PhpWord\TemplateProcessor; +use Symfony\Component\Process\Process; class RecordController extends Controller implements HasMiddleware { @@ -62,29 +64,51 @@ 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'), + $template = new TemplateProcessor(storage_path('app/templates/constancia.docx')); + + $template->setValues([ + '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, + $tempDocx = storage_path('app/temp/constancia_' . $id . '_' . uniqid() . '.docx'); + $template->saveAs($tempDocx); + + $profilePath = storage_path('app/temp/lo_profile_' . uniqid()); + $process = new Process([ + 'libreoffice', + '--headless', + '--convert-to', 'pdf', + '--outdir', storage_path('app/temp'), + $tempDocx, + "-env:UserInstallation=file://{$profilePath}", + ]); + $process->setTimeout(60); + $process->run(); + + @unlink($tempDocx); + + if (!$process->isSuccessful()) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al convertir el documento a PDF.', + 'error' => $process->getErrorOutput(), ]); + } - return $pdf->stream('constancia-inscripcion' . $id . '.pdf'); + $pdfPath = storage_path('app/temp/' . pathinfo($tempDocx, PATHINFO_FILENAME) . '.pdf'); + + return response()->file($pdfPath, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'inline; filename="constancia-inscripcion-' . $id . '.pdf"', + ])->deleteFileAfterSend(true); } /** diff --git a/app/Http/Controllers/Repuve/UpdateController.php b/app/Http/Controllers/Repuve/UpdateController.php index 8d4059f..67bba94 100644 --- a/app/Http/Controllers/Repuve/UpdateController.php +++ b/app/Http/Controllers/Repuve/UpdateController.php @@ -33,8 +33,7 @@ class UpdateController extends Controller implements HasMiddleware public static function middleware(): array { return [ - self::can('updates.vehicle-data', ['vehicleUpdate']), - self::can('updates.vehicle-update', ['updateData']), + self::can('updates.vehicle-data', ['updateData']), self::can('updates.resend-to-repuve', ['resendToRepuve']), ]; } diff --git a/app/Http/Requests/Repuve/VehicleStoreRequest.php b/app/Http/Requests/Repuve/VehicleStoreRequest.php index eeccdb2..3e392b3 100644 --- a/app/Http/Requests/Repuve/VehicleStoreRequest.php +++ b/app/Http/Requests/Repuve/VehicleStoreRequest.php @@ -7,7 +7,7 @@ class VehicleStoreRequest extends FormRequest public function authorize(): bool { - return auth()->user()->can('inscription.vehicle'); + return true; } public function rules(): array diff --git a/app/Http/Requests/Repuve/VehicleUpdateRequest.php b/app/Http/Requests/Repuve/VehicleUpdateRequest.php index d27e3f1..8c1bf99 100644 --- a/app/Http/Requests/Repuve/VehicleUpdateRequest.php +++ b/app/Http/Requests/Repuve/VehicleUpdateRequest.php @@ -9,7 +9,7 @@ class VehicleUpdateRequest extends FormRequest public function authorize(): bool { - return auth()->user()->can('updates.vehicle-update'); + return true; } public function rules(): array diff --git a/composer.json b/composer.json index 0be11af..aa0615a 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "milon/barcode": "^12.0", "notsoweb/laravel-core": "dev-main", "phpoffice/phpspreadsheet": "*", + "phpoffice/phpword": "^1.4", "setasign/fpdf": "^1.8", "spatie/laravel-permission": "^6.16", "spatie/simple-excel": "^3.8", diff --git a/composer.lock b/composer.lock index 5920b69..d4f718a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7a79a1a1e064bf49d5a8cfcf7ee4c69c", + "content-hash": "6a0ffe38dfceb5fa19bb55a5017b4461", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -4653,6 +4653,58 @@ }, "time": "2025-12-30T16:12:18+00:00" }, + { + "name": "phpoffice/math", + "version": "0.3.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/Math.git", + "reference": "fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/Math/zipball/fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a", + "reference": "fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xml": "*", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^7.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\Math\\": "src/Math/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Progi1984", + "homepage": "https://lefevre.dev" + } + ], + "description": "Math - Manipulate Math Formula", + "homepage": "https://phpoffice.github.io/Math/", + "keywords": [ + "MathML", + "officemathml", + "php" + ], + "support": { + "issues": "https://github.com/PHPOffice/Math/issues", + "source": "https://github.com/PHPOffice/Math/tree/0.3.0" + }, + "time": "2025-05-29T08:31:49+00:00" + }, { "name": "phpoffice/phpspreadsheet", "version": "5.4.0", @@ -4762,6 +4814,114 @@ }, "time": "2026-01-11T04:52:00+00:00" }, + { + "name": "phpoffice/phpword", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PHPWord.git", + "reference": "6d75328229bc93790b37e93741adf70646cea958" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PHPWord/zipball/6d75328229bc93790b37e93741adf70646cea958", + "reference": "6d75328229bc93790b37e93741adf70646cea958", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-gd": "*", + "ext-json": "*", + "ext-xml": "*", + "ext-zip": "*", + "php": "^7.1|^8.0", + "phpoffice/math": "^0.3" + }, + "require-dev": { + "dompdf/dompdf": "^2.0 || ^3.0", + "ext-libxml": "*", + "friendsofphp/php-cs-fixer": "^3.3", + "mpdf/mpdf": "^7.0 || ^8.0", + "phpmd/phpmd": "^2.13", + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": ">=7.0", + "symfony/process": "^4.4 || ^5.0", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Allows writing PDF", + "ext-xmlwriter": "Allows writing OOXML and ODF", + "ext-xsl": "Allows applying XSL style sheet to headers, to main document part, and to footers of an OOXML template" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpWord\\": "src/PhpWord" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-only" + ], + "authors": [ + { + "name": "Mark Baker" + }, + { + "name": "Gabriel Bull", + "email": "me@gabrielbull.com", + "homepage": "http://gabrielbull.com/" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net/blog/" + }, + { + "name": "Ivan Lanin", + "homepage": "http://ivan.lanin.org" + }, + { + "name": "Roman Syroeshko", + "homepage": "http://ru.linkedin.com/pub/roman-syroeshko/34/a53/994/" + }, + { + "name": "Antoine de Troostembergh" + } + ], + "description": "PHPWord - A pure PHP library for reading and writing word processing documents (OOXML, ODF, RTF, HTML, PDF)", + "homepage": "https://phpoffice.github.io/PHPWord/", + "keywords": [ + "ISO IEC 29500", + "OOXML", + "Office Open XML", + "OpenDocument", + "OpenXML", + "PhpOffice", + "PhpWord", + "Rich Text Format", + "WordprocessingML", + "doc", + "docx", + "html", + "odf", + "odt", + "office", + "pdf", + "php", + "reader", + "rtf", + "template", + "template processor", + "word", + "writer" + ], + "support": { + "issues": "https://github.com/PHPOffice/PHPWord/issues", + "source": "https://github.com/PHPOffice/PHPWord/tree/1.4.0" + }, + "time": "2025-06-05T10:32:36+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.5", diff --git a/database/seeders/ModuleSeeder.php b/database/seeders/ModuleSeeder.php index c70b852..eef3fe7 100644 --- a/database/seeders/ModuleSeeder.php +++ b/database/seeders/ModuleSeeder.php @@ -18,8 +18,6 @@ public function run(): void 'name' => 'MODULO PARQUE LA CHOCA', 'municipality_id' => 4, 'address' => 'ESTACIONAMIENTO DEL PARQUE LA CHOCA, CENTRO, TABASCO', - 'colony' => 'CENTRO', - 'cp' => null, 'longitude' => -92.954605, 'latitude' => 18.004537, 'status' => true, @@ -28,8 +26,6 @@ public function run(): void 'name' => 'MODULO FINANZAS BASE 4', 'municipality_id' => 4, 'address' => 'ESTACIONAMIENTO DE FINANZAS BASE 4, CENTRO, TABASCO', - 'colony' => 'CASA BLANCA', - 'cp' => null, 'longitude' => -92.923486, 'latitude' => 18.001417, 'status' => true, @@ -38,8 +34,6 @@ public function run(): void 'name' => 'MODULO CARDENAS', 'municipality_id' => 2, 'address' => 'ESTACIONAMIENTO DE LA POLICIA ESTATAL DE CAMINOS, CARDENAS, TABASCO', - 'colony' => 'SANTA MARIA DE GUADALUPE', - 'cp' => null, 'longitude' => -93.362824, 'latitude' => 17.996747, 'status' => true, @@ -48,8 +42,6 @@ public function run(): void 'name' => 'MODULO PASEO DE LA SIERRA', 'municipality_id' => 4, 'address' => 'ESTACIONAMIENTO PASEO DE LA SECRETARIA DE FINANZAS, CENTRO, TABASCO', - 'colony' => 'REFORMA', - 'cp' => null, 'longitude' => -92.929378, 'latitude' => 17.981033, 'status' => true, @@ -58,8 +50,6 @@ public function run(): void 'name' => 'CENTRO DE ACTIVACION COMALCALCO', 'municipality_id' => 5, 'address' => '', - 'colony' => 'ESTACIONAMIENTO DE LA POLICIA ESTATAL DE CAMINOS, COMALCALCO, TABASCO', - 'cp' => null, 'longitude' => -93.218679, 'latitude' => 18.264577, 'status' => true, diff --git a/database/seeders/RoleSeeder.php b/database/seeders/RoleSeeder.php index 616d654..ff6050e 100644 --- a/database/seeders/RoleSeeder.php +++ b/database/seeders/RoleSeeder.php @@ -78,7 +78,6 @@ public function run(): void // === INSCRIPCIONES === $inscriptions = PermissionType::updateOrCreate(['name' => 'Proceso de Sustitución por primera vez']); - $inscriptionVehicle = $this->onPermission('inscription.vehicle', 'Inscribir vehículo', $inscriptions, 'api'); $inscriptionSearchNational = $this->onPermission('inscription.search.national', 'Buscar en consulta nacional', $inscriptions, 'api'); $inscriptionSearch = $this->onPermission('inscription.search', 'Buscar en consulta', $inscriptions, 'api'); @@ -93,10 +92,9 @@ public function run(): void $systemSettings = $this->onPermission('system.settings', 'Configurar credenciales REPUVE', $system, 'api'); // === ACTUALIZAR REGISTRO === - $updates = PermissionType::updateOrCreate(['name' => 'Actualizar Registro']); + $updates = PermissionType::updateOrCreate(['name' => 'Enviar registros a REPUVE Nacional']); $updateVehicleData = $this->onPermission('updates.vehicle-data', 'Actualizar datos de vehículo por formulario', $updates, 'api'); - $updateVehicleUpdate = $this->onPermission('updates.vehicle-update', 'Actualizar datos de vehículo', $updates, 'api'); $updateResendToRepuve = $this->onPermission('updates.resend-to-repuve', 'Reenviar a REPUVE', $updates, 'api'); // === GENERAR FORMATOS === @@ -171,11 +169,11 @@ public function run(): void // Sistema $systemSettings, // Inscripciones - $inscriptionVehicle, $inscriptionSearch, $inscriptionSearchNational, + $inscriptionSearch, $inscriptionSearchNational, // Cancelaciones $cancellationTagNoAsignado, // Actualizaciones - $updateVehicleData, $updateVehicleUpdate, $updateResendToRepuve, + $updateVehicleData, $updateResendToRepuve, // Generar formatos $recordGeneratePdf, $recordGeneratePdfVerification, $recordGeneratePdfConstancia, $recordGeneratePdfForm, // Reportes @@ -195,11 +193,11 @@ public function run(): void // Dispositivos $deviceIndex, $deviceCreate, $deviceEdit, $deviceDestroy, $deviceToggleStatus, // Inscripciones - $inscriptionVehicle, $inscriptionSearch, $inscriptionSearchNational, + $inscriptionSearch, $inscriptionSearchNational, // Cancelaciones $cancellationTagNoAsignado, // Actualizaciones - $updateVehicleData, $updateVehicleUpdate, + $updateVehicleData, // Generar formatos $recordGeneratePdf, $recordGeneratePdfVerification, $recordGeneratePdfConstancia, $recordGeneratePdfForm, // Reportes diff --git a/storage/app/.gitignore b/storage/app/.gitignore index 1d57501..4a70c96 100755 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -4,4 +4,5 @@ !private/ !profile/ !public/ +!templates/ !.gitignore diff --git a/storage/app/templates/.gitignore b/storage/app/templates/.gitignore new file mode 100755 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/templates/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore