diff --git a/app/Http/Controllers/Repuve/LogsController.php b/app/Http/Controllers/Repuve/LogsController.php
new file mode 100644
index 0000000..adf97ec
--- /dev/null
+++ b/app/Http/Controllers/Repuve/LogsController.php
@@ -0,0 +1,54 @@
+filters($request);
+ $logs = $this->logsService->readRepuve($filters);
+
+ return ApiResponse::OK->response([
+ 'source' => 'repuve',
+ 'filters' => $filters,
+ 'logs' => $logs,
+ ]);
+ }
+
+ public function padronEstatalLogs(Request $request)
+ {
+ $filters = $this->filters($request);
+ $logs = $this->logsService->readPadronEstatal($filters);
+
+ return ApiResponse::OK->response([
+ 'source' => 'padron-estatal',
+ 'filters' => $filters,
+ 'logs' => $logs,
+ ]);
+ }
+
+ private function filters(Request $request): array
+ {
+ return $request->validate([
+ 'start_date' => ['nullable', 'date'],
+ 'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
+ ]);
+ }
+}
diff --git a/app/Http/Requests/Repuve/VehicleUpdateRequest.php b/app/Http/Requests/Repuve/VehicleUpdateRequest.php
index 8c1bf99..0ae94de 100644
--- a/app/Http/Requests/Repuve/VehicleUpdateRequest.php
+++ b/app/Http/Requests/Repuve/VehicleUpdateRequest.php
@@ -29,7 +29,7 @@ public function rules(): array
'vehicle.rfv' => 'nullable|string|max:50',
'vehicle.rfc' => 'nullable|string|max:13',
'vehicle.ofcexpedicion' => 'nullable|string|max:100',
- 'vehicle.fechaexpedicion' => 'nullable|date',
+ 'vehicle.fechaexpedicion' => ['nullable', 'date_format:d/m/Y'],
'vehicle.tipo_veh' => 'nullable|string|max:50',
'vehicle.numptas' => 'nullable|string',
'vehicle.observac' => 'nullable|string|max:500',
diff --git a/app/Models/Vehicle.php b/app/Models/Vehicle.php
index e526c9f..153d0c2 100644
--- a/app/Models/Vehicle.php
+++ b/app/Models/Vehicle.php
@@ -36,7 +36,6 @@ class Vehicle extends Model
];
protected $casts = [
- 'fechaexpedicion' => 'date',
'reporte_robo' => 'boolean',
];
diff --git a/app/Services/LogsService.php b/app/Services/LogsService.php
new file mode 100644
index 0000000..af0a430
--- /dev/null
+++ b/app/Services/LogsService.php
@@ -0,0 +1,139 @@
+ 'logs/repuve-nacional.log',
+ 'padron-estatal' => 'logs/padron-estatal.log',
+ ];
+
+ public function readRepuve(array $filters): array
+ {
+ return $this->read('repuve', $filters);
+ }
+
+ public function readPadronEstatal(array $filters): array
+ {
+ return $this->read('padron-estatal', $filters);
+ }
+
+ private function read(string $source, array $filters): array
+ {
+ $path = storage_path($this->sources[$source]);
+
+ if (!is_file($path) || !is_readable($path)) {
+ return [];
+ }
+
+ $entries = $this->parseFile($path);
+
+ return $this->applyFilters($entries, $filters);
+ }
+
+ private function parseFile(string $path): array
+ {
+ $file = new SplFileObject($path, 'r');
+ $entries = [];
+ $current = null;
+
+ while (!$file->eof()) {
+ $line = rtrim((string) $file->fgets(), "\r\n");
+ if ($line === '') {
+ continue;
+ }
+
+ $header = $this->parseHeader($line);
+
+ if ($header) {
+ if ($current) {
+ $entries[] = $this->sanitize($current);
+ }
+ $current = $header;
+ continue;
+ }
+
+ if ($current) {
+ $current['message'] .= "\n" . $line;
+ }
+ }
+
+ if ($current) {
+ $entries[] = $this->sanitize($current);
+ }
+
+ usort($entries, fn($a, $b) => strcmp($b['timestamp'], $a['timestamp']));
+
+ return $entries;
+ }
+
+ private function parseHeader(string $line): ?array
+ {
+ $pattern = '/^\[(?
[^\]]+)\]\s[^\.\s]+\.(?[A-Z]+):\s(?.*)$/';
+
+ if (!preg_match($pattern, $line, $m)) {
+ return null;
+ }
+
+ try {
+ $date = Carbon::parse($m['dt']);
+ } catch (\Throwable $e) {
+ return null;
+ }
+
+ return [
+ 'timestamp' => $date->toDateTimeString(),
+ 'level' => strtolower($m['level']),
+ 'message' => $m['message'],
+ ];
+ }
+
+ private function applyFilters(array $entries, array $filters): array
+ {
+ $start = !empty($filters['start_date']) ? Carbon::parse($filters['start_date'])->startOfDay() : null;
+ $end = !empty($filters['end_date']) ? Carbon::parse($filters['end_date'])->endOfDay() : null;
+ $level = $filters['level'] ?? null;
+
+ return array_values(array_filter($entries, function (array $entry) use ($start, $end, $level) {
+ $dt = Carbon::parse($entry['timestamp']);
+
+ if ($start && $dt->lt($start)) {
+ return false;
+ }
+
+ if ($end && $dt->gt($end)) {
+ return false;
+ }
+
+ if ($level && $entry['level'] !== strtolower($level)) {
+ return false;
+ }
+
+ return true;
+ }));
+ }
+
+ private function sanitize(array $entry): array
+ {
+ $text = $entry['message'];
+
+ $patterns = [
+ '/("password"\s*:\s*")[^"]*(")/i' => '$1***$2',
+ '/("token"\s*:\s*")[^"]*(")/i' => '$1***$2',
+ '/(authorization)\s*[:=]\s*([^,\s]+)/i' => '$1=***',
+ '/()(.*?)(<\/arg0>)/i' => '$1***$3',
+ '/()(.*?)(<\/arg1>)/i' => '$1***$3',
+ ];
+
+ foreach ($patterns as $pattern => $replacement) {
+ $text = preg_replace($pattern, $replacement, $text);
+ }
+
+ $entry['message'] = $text;
+
+ return $entry;
+ }
+}
\ No newline at end of file
diff --git a/app/Services/PadronEstatalService.php b/app/Services/PadronEstatalService.php
index 2a1d1b7..6771016 100644
--- a/app/Services/PadronEstatalService.php
+++ b/app/Services/PadronEstatalService.php
@@ -172,11 +172,7 @@ private function parsearRespuesta(string $soapResponse): array
*/
public function extraerDatosVehiculo(array $datos): array
{
- // Convertir fecha de DD/MM/YYYY a YYYY-MM-DD
- $fechaexpedicion = null;
- if (isset($datos['fechaexp']) && $datos['fechaexp']) {
- $fechaexpedicion = $this->convertirFecha($datos['fechaexp']);
- }
+ $fechaexpedicion = $datos['fechaexp'] ?? null;
return [
'placa' => $datos['placa'] ?? null,
@@ -201,29 +197,6 @@ public function extraerDatosVehiculo(array $datos): array
];
}
- /**
- * Convierte fecha de DD/MM/YYYY a YYYY-MM-DD
- */
- private function convertirFecha(?string $fecha): ?string
- {
- if (!$fecha) {
- return null;
- }
-
- // Si ya está en formato YYYY-MM-DD, retornar tal cual
- if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha)) {
- return $fecha;
- }
-
- // Convertir de DD/MM/YYYY a YYYY-MM-DD
- if (preg_match('/^(\d{2})\/(\d{2})\/(\d{4})$/', $fecha, $matches)) {
- return "{$matches[3]}-{$matches[2]}-{$matches[1]}";
- }
-
- // Si no coincide con ningún formato esperado, retornar null
- return null;
- }
-
/**
* Extrae los datos del propietario del resultado
*/
diff --git a/app/Services/RepuveService.php b/app/Services/RepuveService.php
index 7b77ef8..b7b65ba 100644
--- a/app/Services/RepuveService.php
+++ b/app/Services/RepuveService.php
@@ -236,7 +236,9 @@ private function parseVehicleResponse(string $soapResponse, string $niv)
'pedimento',
'fecha_pedimento',
'clave_importador',
- 'observaciones'
+ 'folio_CI',
+ 'identificador_CI',
+ 'observaciones',
];
$jsonResponse = [];
diff --git a/database/migrations/2026_03_30_085409_change_fechaexpedicion_to_string_in_vehicle_table.php b/database/migrations/2026_03_30_085409_change_fechaexpedicion_to_string_in_vehicle_table.php
new file mode 100644
index 0000000..8f4fb83
--- /dev/null
+++ b/database/migrations/2026_03_30_085409_change_fechaexpedicion_to_string_in_vehicle_table.php
@@ -0,0 +1,28 @@
+string('fechaexpedicion', 10)->nullable()->change();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('vehicle', function (Blueprint $table) {
+ $table->date('fechaexpedicion')->nullable()->change();
+ });
+ }
+};
diff --git a/database/seeders/RoleSeeder.php b/database/seeders/RoleSeeder.php
index 64b2825..56971dd 100644
--- a/database/seeders/RoleSeeder.php
+++ b/database/seeders/RoleSeeder.php
@@ -153,6 +153,11 @@ public function run(): void
$tagAssignToModule = $this->onPermission('tags.assign_to_module', 'Asignar etiquetas a módulo', $tags, 'api');
+ // === LOGS DE SERVICIOS ===
+ $logsServices = PermissionType::updateOrCreate(['name' => 'Logs de Servicios']);
+
+ $logsServicesIndex = $this->onPermission('logs.services.index', 'Ver logs de servicios externos', $logsServices, 'api');
+
// =========================================================
// ROLES
// =========================================================
@@ -194,7 +199,9 @@ public function run(): void
// Constancias
$tagIndex, $tagCreate, $tagEdit, $tagDestroy,
//app
- $apkIndex
+ $apkIndex,
+ // Logs
+ $logsServicesIndex
);
// Encargado
@@ -219,6 +226,8 @@ public function run(): void
$packageIndex, $packageCreate, $packageEdit, $packageDestroy, $packageBoxTags,
// Constancias
$tagIndex, $tagCreate, $tagEdit, $tagDestroy, $tagAssignToModule,
+ // Logs
+ $logsServicesIndex
);
// Perito
diff --git a/routes/api.php b/routes/api.php
index 893aadc..b1adb18 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -15,6 +15,7 @@
use App\Http\Controllers\Repuve\PackageController;
use App\Http\Controllers\Repuve\TagsController;
use App\Http\Controllers\Repuve\AppController;
+use App\Http\Controllers\Repuve\LogsController;
use App\Http\Controllers\System\SettingsController;
/**
@@ -100,6 +101,10 @@
Route::delete('app/{app}', [AppController::class, 'destroy']);
Route::get('app/download', [AppController::class, 'download']);
+ // Rutas de logs
+ Route::get('logs/repuve', [LogsController::class, 'repuveLogs']);
+ Route::get('logs/padron-estatal', [LogsController::class, 'padronEstatalLogs']);
+
});
/** Rutas públicas */