Correciones a RepuveService, Tags y Module

This commit is contained in:
Juan Felipe Zapata Moreno 2025-11-29 02:49:51 -06:00
parent d8ec98cd7c
commit 75889becaf
7 changed files with 371 additions and 21 deletions

View File

@ -21,7 +21,12 @@ class ModuleController extends Controller
public function index(Request $request)
{
try {
$modules = Module::with(['responsible:id,name,email', 'municipality:id,code,name'])->withCount(['packages']);
$modules = Module::with([
'responsible:id,name,email',
'municipality:id,code,name',
'users:id,name,paternal,maternal,email,module_id',
'users.roles:id,name,description'
])->withCount(['packages']);
// Filtro por nombre
if ($request->filled('name')) {
@ -101,7 +106,12 @@ public function store(ModuleStoreRequest $request)
public function show($id)
{
try {
$modules = Module::with(['responsible:id,name,email', 'municipality:id,code,name'])->withCount(['packages'])->find($id);
$modules = Module::with([
'responsible:id,name,email',
'municipality:id,code,name',
'users:id,name,paternal,maternal,email,module_id',
'users.roles:id,name,description'
])->withCount(['packages'])->find($id);
return ApiResponse::OK->response([
'module' => $modules,

View File

@ -4,18 +4,26 @@
use App\Http\Controllers\Controller;
use App\Models\CatalogTagStatus;
use App\Models\Module;
use Illuminate\Http\Request;
use App\Models\Tag;
use Exception;
use Illuminate\Support\Facades\DB;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
class TagsController extends Controller
{
public function index(Request $request)
{
try {
$tags = Tag::with('vehicle:id,placa,niv', 'package:id,lot,box_number', 'status:id,name')->orderBy('id', 'ASC');
$tags = Tag::with([
'vehicle:id,placa,niv',
'package:id,lot,box_number',
'status:id,code,name',
'module:id,name'
])->orderBy('id', 'ASC');
if ($request->has('status')) {
$tags->whereHas('status', function ($q) use ($request) {
@ -29,6 +37,10 @@ public function index(Request $request)
});
}
if ($request->has('module_id')) {
$tags->where('module_id', $request->module_id);
}
return ApiResponse::OK->response([
'tag' => $tags->paginate(config('app.pagination')),
]);
@ -91,7 +103,7 @@ public function tagStore(Request $request)
DB::beginTransaction();
$statusAvailable = CatalogTagStatus::where('name', Tag::STATUS_AVAILABLE)->first();
$statusAvailable = CatalogTagStatus::where('code', Tag::STATUS_AVAILABLE)->first();
if (!$statusAvailable) {
return ApiResponse::NOT_FOUND->response([
@ -99,6 +111,13 @@ public function tagStore(Request $request)
]);
}
if ($tag->module_id === null) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'El tag no está asignado a ningún módulo. Debe asignarse primero a un módulo antes de poder usarse.',
'tag_number' => $tagNumber,
]);
}
$createdTags = [];
$errors = [];
@ -197,12 +216,13 @@ public function assignToModule(Request $request)
DB::commit();
return ApiResponse::OK->response([
'message' => 'Tags asignados al módulo correctamente.',
'count' => count($tagIds),
'module_id' => $request->module_id,
'tags_assigned' => $tags->pluck('tag_number')->toArray(),
]);
// Generar PDF de Vale de Entrega
$module = Module::with('users')->findOrFail($request->module_id);
$tagsAssigned = Tag::whereIn('id', $tagIds)->orderBy('folio')->get();
$pdf = $this->generateValeEntregaPdf($module, $tagsAssigned);
return $pdf->download('vale-entrega-modulo-' . $module->id . '-' . date('YmdHis') . '.pdf');
} catch (Exception $e) {
DB::rollback();
return ApiResponse::INTERNAL_ERROR->response([
@ -211,4 +231,28 @@ public function assignToModule(Request $request)
]);
}
}
/**
* Generar PDF de Vale de Entrega
*/
private function generateValeEntregaPdf(Module $module, $tags)
{
// Cargar responsables del módulo con sus roles
$responsables = $module->users()->with('roles')->get();
// Preparar datos para el PDF
$data = [
'module' => $module,
'responsables' => $responsables,
'tags' => $tags,
'total_tags' => $tags->count(),
'fecha' => Carbon::now()->locale('es')->isoFormat('D [de] MMMM [de] YYYY'),
];
//PDF
$pdf = Pdf::loadView('pdfs.delivery', $data);
$pdf->setPaper('letter', 'portrait');
return $pdf;
}
}

View File

@ -68,4 +68,9 @@ public function responsible()
{
return $this->belongsTo(User::class, 'responsible_id');
}
public function users()
{
return $this->hasMany(User::class, 'module_id');
}
}

View File

@ -3,6 +3,7 @@
namespace App\Services;
use Exception;
use App\Models\Error;
class RepuveService
{
@ -72,10 +73,11 @@ private function parseVehicleResponse(string $soapResponse, string $niv)
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
if (!isset($matches[1])) {
$errorFromDb = Error::where('code', '108')->first();
return [
'has_error' => true,
'error_code' => '108',
'error_message' => 'El resultado que devuelve el Web service es de un formato distinto al esperado',
'error_message' => $errorFromDb ? $errorFromDb->description : 'El resultado que devuelve el Web service es de un formato distinto al esperado',
'timestamp' => now()->toDateTimeString(),
'niv' => $niv,
'repuve_response' => null,
@ -84,21 +86,21 @@ private function parseVehicleResponse(string $soapResponse, string $niv)
$contenido = trim($matches[1]);
// Verificar si hay error
// Verificar si hay error de REPUVE Nacional
if (str_starts_with($contenido, 'ERROR:')) {
$errorMessage = str_replace('ERROR:', '', $contenido);
// Intentar extraer código de error del mensaje (formato: "ERROR:461|Mensaje")
$errorCode = '-1'; // Por defecto: Error Interno
$errorCode = '-1';
if (preg_match('/^(\d+)\|/', $errorMessage, $codeMatch)) {
$errorCode = $codeMatch[1];
$errorMessage = substr($errorMessage, strlen($errorCode) + 1);
}
// Mensaje directo de REPUVE Nacional
return [
'has_error' => true,
'error_code' => $errorCode,
'error_message' => trim($errorMessage),
'error_message' => trim($errorMessage), // Mensaje directo de REPUVE
'timestamp' => now()->toDateTimeString(),
'niv' => $niv,
'repuve_response' => null,
@ -154,10 +156,11 @@ private function parseVehicleResponse(string $soapResponse, string $niv)
];
}
$errorFromDb = Error::where('code', '108')->first();
return [
'has_error' => true,
'error_code' => '108',
'error_message' => 'El resultado que devuelve el Web service es de un formato distinto al esperado',
'error_message' => $errorFromDb ? $errorFromDb->description : 'El resultado que devuelve el Web service es de un formato distinto al esperado',
'timestamp' => now()->toDateTimeString(),
'niv' => $niv,
'repuve_response' => null,
@ -275,10 +278,11 @@ public function inscribirVehiculo(array $datos)
]);
if ($curlError) {
$errorFromDb = Error::where('code', '103')->first();
return [
'has_error' => true,
'error_code' => '103',
'error_message' => "Error de conexión al Web Service desde el cliente: {$curlError}",
'error_message' => $errorFromDb ? $errorFromDb->description : "Error de conexión al Web Service: {$curlError}",
'timestamp' => now()->toDateTimeString(),
'http_code' => $httpCode,
'raw_response' => $response,
@ -286,10 +290,11 @@ public function inscribirVehiculo(array $datos)
}
if ($httpCode !== 200) {
$errorFromDb = Error::where('code', '-1')->first();
return [
'has_error' => true,
'error_code' => '-1',
'error_message' => "Error interno del web service. Código HTTP {$httpCode}",
'error_message' => $errorFromDb ? $errorFromDb->description : "Error interno del web service. Código HTTP {$httpCode}",
'timestamp' => now()->toDateTimeString(),
'http_code' => $httpCode,
'raw_response' => $response,
@ -308,10 +313,11 @@ private function parsearRespuestaInscripcion(string $soapResponse)
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
if (!isset($matches[1])) {
$errorFromDb = Error::where('code', '108')->first();
return [
'has_error' => true,
'error_code' => '108',
'error_message' => 'El resultado que devuelve el Web service es de un formato distinto al esperado',
'error_message' => $errorFromDb ? $errorFromDb->description : 'El resultado que devuelve el Web service es de un formato distinto al esperado',
'timestamp' => now()->toDateTimeString(),
'raw_response' => $soapResponse,
'repuve_response' => null,
@ -329,10 +335,11 @@ private function parsearRespuestaInscripcion(string $soapResponse)
$errorMessage = substr($errorMessage, strlen($errorCode) + 1);
}
// Mensaje directo de REPUVE Nacional
return [
'has_error' => true,
'error_code' => $errorCode,
'error_message' => trim($errorMessage),
'error_message' => trim($errorMessage), // Mensaje directo de REPUVE
'timestamp' => now()->toDateTimeString(),
'raw_response' => $soapResponse,
'repuve_response' => null,
@ -352,10 +359,11 @@ private function parsearRespuestaInscripcion(string $soapResponse)
],
];
} else {
$errorFromDb = Error::where('code', '108')->first();
return [
'has_error' => true,
'error_code' => '108',
'error_message' => 'El resultado que devuelve el Web service es de un formato distinto al esperado',
'error_message' => $errorFromDb ? $errorFromDb->description : 'El resultado que devuelve el Web service es de un formato distinto al esperado',
'timestamp' => now()->toDateTimeString(),
'raw_response' => $soapResponse,
'repuve_response' => null,

View File

@ -0,0 +1,282 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Vale de Entrega de Constancias</title>
<style>
/* CONFIGURACIÓN DE PÁGINA */
@page {
margin: 1.2cm 1.5cm;
size: letter;
}
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 10pt;
line-height: 1.3;
color: #000;
margin: 0;
padding: 0;
}
.container {
width: 100%;
margin: 0 auto;
}
/* HEADER */
.header {
width: 100%;
margin-bottom: 10px;
}
.header-table {
width: 100%;
border-collapse: collapse;
}
.header-left {
width: 25%;
vertical-align: top;
}
.header-center {
width: 40%;
text-align: center;
vertical-align: top;
}
.header-right {
width: 35%;
text-align: right;
vertical-align: top;
font-size: 8pt;
}
.logo {
max-height: 50px;
width: auto;
}
.header-title {
font-size: 9pt;
font-weight: bold;
line-height: 1.2;
margin: 0;
}
/* CUERPO DEL DOCUMENTO */
.date-location {
text-align: right;
font-size: 9pt;
margin-bottom: 12px;
}
.title {
text-align: center;
font-weight: bold;
font-size: 12pt;
margin: 10px 0;
text-decoration: underline;
}
.content {
text-align: justify;
margin-bottom: 12px;
font-size: 10pt;
}
/* LISTA DE TAGS */
.tag-list {
margin: 10px 0;
font-size: 9pt;
}
.tag-columns {
column-count: 3;
column-gap: 15px;
}
.tag-item {
margin: 2px 0;
break-inside: avoid;
}
/* --- ZONA DE FIRMAS (CSS IMPORTANTE AQUÍ) --- */
.firmas-container {
margin-top: 40px; /* Más espacio antes de empezar las firmas */
width: 100%;
}
.firma-titulo {
font-weight: bold;
font-size: 10pt;
margin-bottom: 15px;
text-align: left;
}
/* Contenedor para centrar la firma única de Entrega */
.firma-entrega-wrapper {
width: 100%;
text-align: center;
margin-bottom: 30px;
}
/* Contenedor general de Reciben */
.reciben-wrapper {
width: 100%;
text-align: center; /* Esto centra las firmas horizontalmente */
font-size: 0; /* Truco para eliminar espacios fantasmas entre inline-blocks */
}
/* CAJA INDIVIDUAL DE FIRMA (RECIBEN) */
.firma-reciben-item {
width: 44%; /* Menos del 50% para que quepan 2 */
display: inline-block;
vertical-align: top;
margin-left: 2%;
margin-right: 2%;
margin-bottom: 60px; /* <--- ESTO ARREGLA QUE ESTÉN PEGADOS VERTICALMENTE */
font-size: 10pt; /* Restauramos tamaño de letra */
}
/* CAJA INDIVIDUAL DE ENTREGA */
.firma-entrega-box {
display: inline-block;
width: 50%; /* Ancho controlado para que no ocupe toda la hoja */
}
.firma-linea {
border-top: 1px solid #000;
padding-top: 5px;
margin-bottom: 3px;
}
.firma-nombre {
font-weight: bold;
font-size: 9pt;
text-align: center;
margin: 0;
padding: 0 5px;
}
.firma-cargo {
font-size: 8pt;
text-align: center;
margin: 0;
padding: 0 5px;
}
/* FOOTER */
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
font-size: 7pt;
text-align: center;
border-top: 1px solid #ccc;
padding-top: 5px;
background-color: #fff;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<table class="header-table">
<tr>
<td class="header-left">
<?php
$imagePath = resource_path('images/logo-seguridad.png');
$imageData = base64_encode(file_get_contents($imagePath));
$imageSrc = 'data:image/png;base64,' . $imageData;
?>
<img src="{{ $imageSrc }}" alt="Logo Seguridad" class="logo">
</td>
<td class="header-center">
<p class="header-title">
<strong>SEGURIDAD</strong><br>
SECRETARIA DE SEGURIDAD<br>
Y PROTECCIÓN CIUDADANA
</p>
</td>
<td class="header-right">
<p class="header-title">
Dirección General de la Policía Estatal de Caminos<br>
Dirección de Servicios al Público<br>
Departamento del Registro Público Vehicular
</p>
</td>
</tr>
</table>
</div>
<div class="date-location">
Villahermosa, Tabasco a {{ $fecha }}
</div>
<div class="title">
VALE DE ENTREGA
</div>
<div class="content">
Se realiza la entrega de <strong>{{ $total_tags }}</strong>
constancias de inscripción de segunda generación (moradas) para la realización de trámites del Registro
Público Vehicular, las cuales se enumeran por folios, a continuación.
</div>
<div class="tag-list">
<div class="tag-columns">
@foreach($tags as $index => $tag)
<div class="tag-item">
{{ $index + 1 }}. {{ $tag->folio }}
</div>
@endforeach
</div>
</div>
<div class="firmas-container">
<div>
<div class="firma-titulo">ENTREGA</div>
<div class="firma-entrega-wrapper">
<div class="firma-entrega-box">
<div class="firma-linea">
<p class="firma-nombre">LIC. MARY ISABEL RAMON MADRIGAL</p>
<p class="firma-cargo">JEFA DEL DEPARTAMENTO REPUVE</p>
</div>
</div>
</div>
</div>
<div>
<div class="firma-titulo">RECIBEN</div>
<div class="reciben-wrapper">
@foreach($responsables as $responsable)
<div class="firma-reciben-item">
<div class="firma-linea">
<p class="firma-nombre">
{{ strtoupper($responsable->full_name) }}
</p>
<p class="firma-cargo">
{{ strtoupper($responsable->roles->first()->description ?? 'RESPONSABLE') }} MÓDULO {{ $module->id }}
</p>
</div>
</div>
@endforeach
</div>
</div>
</div>
<div class="footer">
Av. Adolfo Ruiz Cortines, S/N, Colonia Casa Blanca, C.P. 86060, Villahermosa, Tabasco, México<br>
Tel: 9933156230 | www.ssptabasco.gob.mx
</div>
</div>
</body>
</html>

View File

@ -72,6 +72,7 @@
Route::resource('tags', TagsController::class);
Route::post('tags/import', [TagsController::class, 'tagStore']);
Route::post('tags/assign-to-module', [TagsController::class, 'assignToModule']);
Route::get('tags/pdf-vale-entrega', [TagsController::class, 'generateValeEntregaPdf']);
//Rutas de nombres de archivos en catálogo
Route::resource('catalog-name-imgs', CatalogNameImgController::class);