Ver Fonte

modificación a updateVisitStatus para manejar todos los cambios de estado excepto CP que es manejada por registerOperatorClosing y EP que es manejada por StartVisit, función registerOperator para el manejo de los comentarios de finalizacion y envio de notificaciones para todos los cambios anteriores

EmilianoOrtiz há 6 dias atrás
pai
commit
ed6b07b223

+ 532 - 16
sistema-mantenimiento-back/app/Http/Controllers/PreventiveMaintenanceController.php

@@ -2234,6 +2234,7 @@ class PreventiveMaintenanceController extends Controller
     // Visitas técnicas no programadas (Preventivas)
     public function newUnprogrammedOrder(Request $request)
     {
+        Log::info($request->all());
         DB::enableQueryLog();
 
         $validator = Validator::make($request->all(), [
@@ -2347,15 +2348,29 @@ class PreventiveMaintenanceController extends Controller
             'CI' => $form['comments']
         ];
 
+
         $now = $this->functionsController->now();
         $nowStr = $now->toDateTimeString();
+        $statusHistoryStr = json_encode([
+            [
+                'FECHA' => $nowStr,
+                'ESTADO' => 'P',
+                'USUARIO' => $idUser,
+            ]
+        ]);
+
+
         $commentsStr = json_encode($commentsArr);
         $staffStr = json_encode($staff);
         $idVisit = DB::table('S002V01TRVTN')->insertGetId([
             'RVTN_NULI' => $form['linea'],
             'RVTN_EQRE' => $equipmentCode,
+            'RVTN_DEIN' => $form['description'],
+            'RVTN_TIAC' => 'M',
             'RVTN_PEIN' => $staffStr,
             'RVTN_MAUT' => $form['resources'],
+            'RVTN_ESTA' => 'P',
+            'RVTN_HIES' => $statusHistoryStr,
             'RVTN_COME' => $commentsStr,
             'RVTN_USRE' => $idUser,
             'RVTN_FERE' => $nowStr,
@@ -2624,6 +2639,7 @@ class PreventiveMaintenanceController extends Controller
         // RVTN_ESTA enum: 'VA','EP','CP','CE','P','C','R','A','F'
         // VA, EP, CP, CE: Solo para RVTN_TIAC='A' (Automática)
         // P, C, R, A, F: Solo para RVTN_TIAC='M' (Manual)
+        // EP y C son compartidos entre ambos tipos
         $validator = Validator::make($request->all(), [
             'id_user' => 'required|string',
             'linea' => 'required|integer',
@@ -2668,48 +2684,134 @@ class PreventiveMaintenanceController extends Controller
             ['RVTN_IDVI', '=', $idVisit],
         ])->first();
 
+        if (is_null($visit)) {
+            return $this->responseController->makeResponse(true, 'La visita solicitada no existe.', [], 404);
+        }
+
+        $currentStatus = $visit->RVTN_ESTA;
+        $newStatus = $form['status'];
+        $visitType = $visit->RVTN_TIAC; // 'A' = Automática, 'M' = Manual
+
         // Validar que el estado solicitado corresponda al tipo de activación
         $automaticStates = ['VA', 'EP', 'CP', 'CE'];
         $manualStates = ['P', 'C', 'R', 'A', 'F'];
-        $isRequestedStateAutomatic = in_array($form['status'], $automaticStates);
-        $isRequestedStateManual = in_array($form['status'], $manualStates);
-
-        if (($visit->RVTN_TIAC === 'A' && !$isRequestedStateAutomatic) ||
-            ($visit->RVTN_TIAC === 'M' && !$isRequestedStateManual)
+        $isRequestedStateAutomatic = in_array($newStatus, $automaticStates);
+        $isRequestedStateManual = in_array($newStatus, $manualStates);
+
+        // EP y C son compartidos, ajustar validación
+        if ($newStatus === 'EP' || $newStatus === 'C') {
+            // Permitir EP y C para ambos tipos
+        } elseif (($visitType === 'A' && !$isRequestedStateAutomatic) ||
+            ($visitType === 'M' && !$isRequestedStateManual)
         ) {
-            return $this->responseController->makeResponse(true, "El estado {$form['status']} no corresponde al tipo de activación de la visita.", [], 400);
+            return $this->responseController->makeResponse(true, "El estado {$newStatus} no corresponde al tipo de activación de la visita.", [], 400);
+        }
+
+        // Matriz de transiciones permitidas
+        $allowedTransitions = [
+            'A' => [ // Visitas Automáticas
+                'VA' => ['EP', 'C'],  // Validado -> En Proceso o Cancelar
+                'EP' => ['CP', 'C'],  // En Proceso -> Cerrado Pendiente o Cancelar
+                'CP' => ['CE', 'C'],  // Cerrado Pendiente -> Cerrado o Cancelar (aunque C desde CP está bloqueado)
+                'CE' => []            // Cerrado - Estado final, no se puede cambiar
+            ],
+            'M' => [ // Visitas Manuales
+                'P' => ['A', 'R', 'C'], // Pendiente -> Aprobar, Rechazar o Cancelar
+                'A' => ['EP', 'C'],     // Aprobado -> En Proceso o Cancelar
+                'EP' => ['F', 'C'],     // En Proceso -> Finalizar o Cancelar
+                'F' => [],              // Finalizado - Estado final, no se puede cambiar
+                'R' => []               // Rechazado - Estado final, no se puede cambiar
+            ]
+        ];
+
+        // Validar transición permitida
+        $currentStatusTransitions = $allowedTransitions[$visitType][$currentStatus] ?? [];
+
+        // Si el estado actual no está en la matriz, no permitir cambios
+        if (!array_key_exists($currentStatus, $allowedTransitions[$visitType])) {
+            return $this->responseController->makeResponse(true, "El estado actual {$currentStatus} no permite cambios.", [], 400);
+        }
+
+        // Validar si la transición está permitida
+        if (!in_array($newStatus, $currentStatusTransitions)) {
+            $statusNames = [
+                'VA' => 'Validado',
+                'EP' => 'En Proceso',
+                'CP' => 'Cerrado Pendiente',
+                'CE' => 'Cerrado',
+                'P' => 'Pendiente',
+                'A' => 'Aprobado',
+                'R' => 'Rechazado',
+                'F' => 'Finalizado',
+                'C' => 'Cancelado'
+            ];
+            $currentStatusName = $statusNames[$currentStatus] ?? $currentStatus;
+            $newStatusName = $statusNames[$newStatus] ?? $newStatus;
+            return $this->responseController->makeResponse(true, "No se puede cambiar de {$currentStatusName} a {$newStatusName}. Transición no permitida.", [], 400);
         }
 
+        // Validar cancelación desde estados finales
+        if ($newStatus === 'C') {
+            $finalStates = $visitType === 'A' ? ['CE'] : ['F', 'R'];
+            if (in_array($currentStatus, $finalStates)) {
+                return $this->responseController->makeResponse(true, "No se puede cancelar una visita que ya está en estado final.", [], 400);
+            }
+            // También bloquear desde CP según aclaración
+            if ($currentStatus === 'CP') {
+                return $this->responseController->makeResponse(true, "No se puede cancelar una visita en estado Cerrado Pendiente.", [], 400);
+            }
+        }
+
+        // Actualizar comentarios
         $commentsArr = json_decode($visit->RVTN_COME, true);
-        $commentsArr["C$form[status]"] = $form['comments'];
+        if (!is_array($commentsArr)) {
+            $commentsArr = [];
+        }
+        $commentsArr["C$newStatus"] = $form['comments'];
         $commentsStr = json_encode($commentsArr);
 
+        // Obtener y actualizar historial de estados
+        $statusHistoryArr = json_decode($visit->RVTN_HIES, true);
+        if (!is_array($statusHistoryArr)) {
+            $statusHistoryArr = [];
+        }
+
         $now = $this->functionsController->now();
         $nowStr = $now->toDateTimeString();
 
+        // Agregar nuevo estado al historial
+        $statusHistoryArr[] = [
+            'FECHA' => $nowStr,
+            'ESTADO' => $newStatus,
+            'USUARIO' => $idUser
+        ];
+        $statusHistoryStr = json_encode($statusHistoryArr);
+
+        // Preparar actualización
         $updateArr = [
-            'RVTN_ESTA' => $form['status'],
+            'RVTN_ESTA' => $newStatus,
             'RVTN_COME' => $commentsStr,
+            'RVTN_HIES' => $statusHistoryStr,
             'RVTN_USMO' => $idUser,
             'RVTN_FEMO' => $nowStr,
         ];
 
         // Estados automáticos: VA, EP, CP, CE
-        if (in_array($form['status'], ['VA', 'EP', 'CP', 'CE'])) {
+        if (in_array($newStatus, ['VA', 'EP', 'CP', 'CE'])) {
             $updateArr['RVTN_UARE'] = $idUser;
             $updateArr['RVTN_FARE'] = $nowStr;
         }
 
         // Estados manuales
-        if ($form['status'] == 'P') {
+        if ($newStatus == 'P') {
             // Pendiente - Ya registrado en creación
-        } elseif ($form['status'] == 'C') {
+        } elseif ($newStatus == 'C') {
             $updateArr['RVTN_USCA'] = $idUser;
             $updateArr['RVTN_FECA'] = $nowStr;
-        } elseif ($form['status'] == 'R' || $form['status'] == 'A') {
+        } elseif ($newStatus == 'R' || $newStatus == 'A') {
             $updateArr['RVTN_UARE'] = $idUser;
             $updateArr['RVTN_FARE'] = $nowStr;
-        } elseif ($form['status'] == 'F') {
+        } elseif ($newStatus == 'F') {
             $updateArr['RVTN_USFI'] = $idUser;
             $updateArr['RVTN_FEFI'] = $nowStr;
         }
@@ -2719,6 +2821,75 @@ class PreventiveMaintenanceController extends Controller
             ['RVTN_IDVI', '=', $idVisit],
         ])->update($updateArr);
 
+        // Obtener audiencia para notificaciones (operarios y regulador)
+        $audience = [];
+
+        // Obtener operarios que aceptaron la invitación (del último estado VA)
+        $statusHistoryForAudience = json_decode($visit->RVTN_HIES, true);
+        if (is_array($statusHistoryForAudience)) {
+            // Buscar el último estado VA
+            $lastVAStatus = null;
+            for ($i = count($statusHistoryForAudience) - 1; $i >= 0; $i--) {
+                if ($statusHistoryForAudience[$i]['ESTADO'] === 'VA') {
+                    $lastVAStatus = $statusHistoryForAudience[$i];
+                    break;
+                }
+            }
+
+            if ($lastVAStatus && isset($lastVAStatus['ATENCION']) && is_array($lastVAStatus['ATENCION'])) {
+                foreach ($lastVAStatus['ATENCION'] as $item) {
+                    if (($item['RESPUESTA'] ?? '') === 'A') {
+                        $audience[] = $item['ID'];
+                    }
+                }
+            }
+        }
+
+        // Agregar regulador (quien registró la visita)
+        if (!empty($visit->RVTN_USRE) && !in_array($visit->RVTN_USRE, $audience)) {
+            $audience[] = $visit->RVTN_USRE;
+        }
+
+        // Preparar mensajes de notificación según el estado
+        $statusMessages = [
+            'A' => 'Aprobación',
+            'R' => 'Rechazo',
+            'C' => 'Cancelación',
+            'EP' => 'Inicio',
+            'F' => 'Finalización',
+            'VA' => 'Validación',
+            'CP' => 'Cerrado Pendiente',
+            'CE' => 'Cierre'
+        ];
+
+        $statusAction = $statusMessages[$newStatus] ?? 'Cambio de estado';
+        $notificationTitle = "Visita Técnica No Programada #$idVisit";
+        $notificationMessage = "La visita técnica no programada #$idVisit ha sido {$statusAction}.";
+
+        // Notificación general para todos los cambios de estado (A, R, C, EP, F, VA, CE, CP)
+        if (in_array($newStatus, ['A', 'R', 'C', 'EP', 'F', 'VA', 'CE', 'CP']) && !empty($audience)) {
+            $this->notificationsController->emitNotification(
+                'S002V01M10GMPR',
+                $notificationTitle,
+                $notificationMessage,
+                [[
+                    'BOTON' => 'Ver detalles',
+                    'FUNCION' => 'openPreventiveWorkOrderDetails',
+                    'PARAMETROS' => json_encode([$this->encryptionController->encrypt($idVisit)])
+                ], [
+                    'BOTON' => 'Ir al módulo',
+                    'FUNCION' => 'openModule',
+                    'PARAMETROS' => json_encode([$this->encryptionController->encrypt('GMPR/AOTR/RVTP')])
+                ]],
+                $audience,
+                $idUser,
+                $form['linea'],
+                $this->getSocketClient(),
+                $idVisit,
+                'Preventivo'
+            );
+        }
+
         $actions = DB::getQueryLog();
         $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
 
@@ -2738,6 +2909,342 @@ class PreventiveMaintenanceController extends Controller
         return $this->responseController->makeResponse(false, 'EXITO.');
     }
 
+    // Visitas técnicas no programadas (Preventivas)
+    // Registrar comentario de finalización de un operario en estado CP
+    public function registerOperatorClosingComment(Request $request)
+    {
+        DB::enableQueryLog();
+
+        $validator = Validator::make($request->all(), [
+            'id_user' => 'required|string',
+            'linea' => 'required|integer',
+            'id_visit' => 'required|string',
+            'comment' => 'required|string|min:35'
+        ]);
+
+        if ($validator->fails()) {
+            return $this->responseController->makeResponse(
+                true,
+                "Se encontraron uno o más errores.",
+                $this->responseController->makeErrors(
+                    $validator->errors()->messages()
+                ),
+                401
+            );
+        }
+
+        $form = $request->all();
+        $idUser = $this->encryptionController->decrypt($form['id_user']);
+        if (!$idUser) {
+            return $this->responseController->makeResponse(true, 'El ID del usuario que realizó la solicitud no está encriptado correctamente.', [], 400);
+        }
+
+        $usr = DB::table('S002V01TUSUA')->where([
+            ['USUA_NULI', '=', $form['linea']],
+            ['USUA_IDUS', '=', $idUser],
+        ])->first();
+
+        if (is_null($usr)) {
+            return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado.', [], 404);
+        }
+
+        $idVisit = $this->encryptionController->decrypt($form['id_visit']);
+        if (!$idVisit) {
+            return $this->responseController->makeResponse(true, 'El ID de la visita solicitada no está encriptado correctamente.', [], 400);
+        }
+
+        $visit = DB::table('S002V01TRVTN')->where([
+            ['RVTN_NULI', '=', $form['linea']],
+            ['RVTN_IDVI', '=', $idVisit],
+        ])->first();
+
+        if (is_null($visit)) {
+            return $this->responseController->makeResponse(true, 'La visita solicitada no existe.', [], 404);
+        }
+
+        // Validar que la visita está en estado EP o CP
+        if (!in_array($visit->RVTN_ESTA, ['EP', 'CP'])) {
+            return $this->responseController->makeResponse(true, 'La visita no está en estado En Proceso o Cerrado Pendiente. Solo se pueden registrar comentarios de finalización en estos estados.', [], 400);
+        }
+
+        // Iniciar transacción
+        DB::beginTransaction();
+
+        try {
+            // Obtener historial de estados
+            $statusHistoryArr = json_decode($visit->RVTN_HIES, true);
+            if (!is_array($statusHistoryArr)) {
+                DB::rollBack();
+                return $this->responseController->makeResponse(true, 'No se encontró un historial de estados válido para la visita.', [], 500);
+            }
+
+            // Buscar el último estado VA para obtener la lista de operarios que aceptaron
+            $lastVAStatus = null;
+            $acceptedOperators = [];
+            for ($i = count($statusHistoryArr) - 1; $i >= 0; $i--) {
+                if ($statusHistoryArr[$i]['ESTADO'] === 'VA') {
+                    $lastVAStatus = $statusHistoryArr[$i];
+                    break;
+                }
+            }
+
+            if (!$lastVAStatus || !isset($lastVAStatus['ATENCION']) || !is_array($lastVAStatus['ATENCION'])) {
+                DB::rollBack();
+                return $this->responseController->makeResponse(true, 'No se encontró información de operarios asignados a la visita.', [], 404);
+            }
+
+            // Obtener lista de operarios que aceptaron
+            foreach ($lastVAStatus['ATENCION'] as $item) {
+                if (($item['RESPUESTA'] ?? '') === 'A') {
+                    $acceptedOperators[] = $item['ID'];
+                }
+            }
+
+            // Validar que el usuario es uno de los operarios que aceptó
+            if (!in_array($idUser, $acceptedOperators)) {
+                DB::rollBack();
+                return $this->responseController->makeResponse(true, 'El usuario no está autorizado para registrar comentarios de finalización. Solo los operarios que aceptaron la invitación pueden registrar comentarios.', [], 401);
+            }
+
+            // Buscar el objeto CP en el historial o crearlo si no existe
+            $cpStatusIndex = null;
+            $cpStatus = null;
+            $isFirstComment = false;
+            for ($i = count($statusHistoryArr) - 1; $i >= 0; $i--) {
+                if ($statusHistoryArr[$i]['ESTADO'] === 'CP') {
+                    $cpStatusIndex = $i;
+                    $cpStatus = $statusHistoryArr[$i];
+                    break;
+                }
+            }
+
+            // Si no existe objeto CP, crear uno nuevo y cambiar estado de EP a CP
+            if ($cpStatusIndex === null) {
+                $now = $this->functionsController->now();
+                $nowStr = $now->toDateTimeString();
+                $isFirstComment = true;
+
+                $cpStatus = [
+                    'FECHA' => $nowStr,
+                    'ESTADO' => 'CP',
+                    'USUARIO' => $idUser,
+                    'COMENTARIOS_OPERARIOS' => []
+                ];
+                $cpStatusIndex = count($statusHistoryArr);
+                $statusHistoryArr[] = $cpStatus;
+            } else {
+                // Asegurar que existe el array de comentarios
+                if (!isset($cpStatus['COMENTARIOS_OPERARIOS']) || !is_array($cpStatus['COMENTARIOS_OPERARIOS'])) {
+                    $cpStatus['COMENTARIOS_OPERARIOS'] = [];
+                }
+            }
+
+            // Validar que el operario no haya registrado comentario ya
+            foreach ($cpStatus['COMENTARIOS_OPERARIOS'] as $existingComment) {
+                if (isset($existingComment['ID']) && $existingComment['ID'] === $idUser) {
+                    DB::rollBack();
+                    return $this->responseController->makeResponse(true, 'Ya ha registrado su comentario de finalización para esta visita.', [], 400);
+                }
+            }
+
+            // Registrar el comentario del operario
+            $now = $this->functionsController->now();
+            $nowStr = $now->toDateTimeString();
+
+            $operatorName = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
+
+            $cpStatus['COMENTARIOS_OPERARIOS'][] = [
+                'ID' => $idUser,
+                'COMENTARIO' => $form['comment'],
+                'FECHA' => $nowStr
+            ];
+
+            // Actualizar el objeto en el historial
+            $statusHistoryArr[$cpStatusIndex] = $cpStatus;
+            $statusHistoryStr = json_encode($statusHistoryArr);
+
+            // Contar comentarios registrados vs total de operarios
+            $commentsRegistered = count($cpStatus['COMENTARIOS_OPERARIOS']);
+            $totalOperators = count($acceptedOperators);
+            $isComplete = $commentsRegistered >= $totalOperators;
+
+            // Preparar actualización de la visita
+            $updateArr = [
+                'RVTN_HIES' => $statusHistoryStr,
+                'RVTN_USMO' => $idUser,
+                'RVTN_FEMO' => $nowStr,
+            ];
+
+            // Si es el primer comentario, cambiar estado de EP a CP
+            if ($isFirstComment) {
+                $updateArr['RVTN_ESTA'] = 'CP';
+                $updateArr['RVTN_UARE'] = $idUser;
+                $updateArr['RVTN_FARE'] = $nowStr;
+            }
+
+            // Actualizar comentarios si es necesario
+            $commentsArr = json_decode($visit->RVTN_COME, true);
+            if (!is_array($commentsArr)) {
+                $commentsArr = [];
+            }
+            // Solo agregar comentario de cambio de estado si es el primer comentario (cambio EP -> CP)
+            if ($isFirstComment) {
+                $commentsArr['CCP'] = 'Inicio de registro de comentarios de finalización por operarios.';
+            }
+            $commentsStr = json_encode($commentsArr);
+            $updateArr['RVTN_COME'] = $commentsStr;
+
+            // Actualizar la visita
+            DB::table('S002V01TRVTN')->where([
+                ['RVTN_NULI', '=', $form['linea']],
+                ['RVTN_IDVI', '=', $idVisit],
+            ])->update($updateArr);
+
+            // Obtener regulador para notificaciones
+            $regulatorId = $visit->RVTN_USRE;
+            $regulatorAudience = !empty($regulatorId) ? [$regulatorId] : [];
+
+            // Obtener audiencia completa (operarios y regulador) para notificación de cambio de estado
+            $audience = [];
+            if (!empty($lastVAStatus['ATENCION']) && is_array($lastVAStatus['ATENCION'])) {
+                foreach ($lastVAStatus['ATENCION'] as $item) {
+                    if (($item['RESPUESTA'] ?? '') === 'A') {
+                        $audience[] = $item['ID'];
+                    }
+                }
+            }
+            if (!empty($regulatorId) && !in_array($regulatorId, $audience)) {
+                $audience[] = $regulatorId;
+            }
+
+            // Si es el primer comentario (cambio EP -> CP), notificar a todos
+            if ($isFirstComment && !empty($audience)) {
+                $notificationTitle = "Visita Técnica No Programada #$idVisit";
+                $notificationMessage = "La visita técnica no programada #$idVisit ha pasado a estado Cerrado Pendiente.";
+
+                $this->notificationsController->emitNotification(
+                    'S002V01M10GMPR',
+                    $notificationTitle,
+                    $notificationMessage,
+                    [[
+                        'BOTON' => 'Ver detalles',
+                        'FUNCION' => 'openPreventiveWorkOrderDetails',
+                        'PARAMETROS' => json_encode([$this->encryptionController->encrypt($idVisit)])
+                    ], [
+                        'BOTON' => 'Ir al módulo',
+                        'FUNCION' => 'openModule',
+                        'PARAMETROS' => json_encode([$this->encryptionController->encrypt('GMPR/AOTR/RVTP')])
+                    ]],
+                    $audience,
+                    $idUser,
+                    $form['linea'],
+                    $this->getSocketClient(),
+                    $idVisit,
+                    'Preventivo'
+                );
+            }
+
+            // Notificar al regulador por cada comentario registrado
+            if (!empty($regulatorAudience)) {
+                $notificationTitle = "Visita Técnica No Programada #$idVisit";
+                $notificationMessage = "El usuario $operatorName ($idUser) registró comentario de finalización de la visita #$idVisit.";
+
+                $this->notificationsController->emitNotification(
+                    'S002V01M10GMPR',
+                    $notificationTitle,
+                    $notificationMessage,
+                    [[
+                        'BOTON' => 'Ver detalles',
+                        'FUNCION' => 'openPreventiveWorkOrderDetails',
+                        'PARAMETROS' => json_encode([$this->encryptionController->encrypt($idVisit)])
+                    ], [
+                        'BOTON' => 'Ir al módulo',
+                        'FUNCION' => 'openModule',
+                        'PARAMETROS' => json_encode([$this->encryptionController->encrypt('GMPR/AOTR/RVTP')])
+                    ]],
+                    $regulatorAudience,
+                    $idUser,
+                    $form['linea'],
+                    $this->getSocketClient(),
+                    $idVisit,
+                    'Preventivo'
+                );
+            }
+
+            // Si todos los comentarios están completos, enviar notificación final
+            if ($isComplete && !empty($regulatorAudience)) {
+                $finalNotificationTitle = "Visita Técnica No Programada #$idVisit";
+                $finalNotificationMessage = "La visita #$idVisit ya puede ser cerrada. Todos los operarios han registrado sus comentarios de finalización.";
+
+                $this->notificationsController->emitNotification(
+                    'S002V01M10GMPR',
+                    $finalNotificationTitle,
+                    $finalNotificationMessage,
+                    [[
+                        'BOTON' => 'Cerrar visita',
+                        'FUNCION' => 'closePreventiveVisit',
+                        'PARAMETROS' => json_encode([
+                            $this->encryptionController->encrypt($idVisit),
+                            $this->encryptionController->encrypt($form['linea']),
+                            $this->encryptionController->encrypt($regulatorId)
+                        ])
+                    ], [
+                        'BOTON' => 'Ver detalles',
+                        'FUNCION' => 'openPreventiveWorkOrderDetails',
+                        'PARAMETROS' => json_encode([$this->encryptionController->encrypt($idVisit)])
+                    ], [
+                        'BOTON' => 'Ir al módulo',
+                        'FUNCION' => 'openModule',
+                        'PARAMETROS' => json_encode([$this->encryptionController->encrypt('GMPR/AOTR/RVTP')])
+                    ]],
+                    $regulatorAudience,
+                    $idUser,
+                    $form['linea'],
+                    $this->getSocketClient(),
+                    $idVisit,
+                    'Preventivo'
+                );
+            }
+
+            // Confirmar transacción
+            DB::commit();
+
+            $actions = DB::getQueryLog();
+            $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
+
+            $idac = $this->functionsController->registerActivity(
+                $form['linea'],
+                'S002V01M10GMPR',
+                'S002V01F11RVTP',
+                'S002V01P01REVI',
+                'Actualización',
+                "El usuario $name (" . $usr->USUA_IDUS . ") registró comentario de finalización para la visita no programada #$idVisit.",
+                $idUser,
+                $nowStr,
+                'S002V01S02AOTR'
+            );
+
+            $this->functionsController->registerLog($actions, $idUser, $nowStr, $idac, $form['linea']);
+
+            return $this->responseController->makeResponse(false, 'EXITO.', [
+                'comments_registered' => $commentsRegistered,
+                'total_operators' => $totalOperators,
+                'is_complete' => $isComplete
+            ]);
+        } catch (\Exception $e) {
+            // Revertir transacción en caso de error
+            DB::rollBack();
+
+            Log::error('Error en registerOperatorClosingComment: ' . $e->getMessage());
+            return $this->responseController->makeResponse(
+                true,
+                'Ocurrió un error al registrar el comentario de finalización.',
+                ['error' => $e->getMessage()],
+                500
+            );
+        }
+    }
+
     // Visitas técnicas no programadas (Preventivas)
     public function startPreventiveVisit(Request $request)
     {
@@ -2846,6 +3353,15 @@ class PreventiveMaintenanceController extends Controller
 
         $statusHistoryStr = json_encode($statusHistoryArr);
 
+        /* 
+        $commentsArr = json_decode($visit->RVTN_COME, true);
+        if (!is_array($commentsArr)) {
+            $commentsArr = [];
+        }
+        $commentsArr['COMENTARIO_INICIO'] = $form['comments'];
+        $commentsStr = json_encode($commentsArr);
+        */
+
         DB::table('S002V01TRVTN')->where([
             ['RVTN_IDVI', '=', $idVisit],
             ['RVTN_NULI', '=', $form['linea']]
@@ -2858,8 +3374,8 @@ class PreventiveMaintenanceController extends Controller
 
         $this->notificationsController->emitNotification(
             'S002V01M10GMPR',
-            "Visita de mantenimiento preventivo #$idVisit",
-            "Inicio de la ejecución de la visita de mantenimiento preventivo #$idVisit.",
+            "Visita Técnica No Programada #$idVisit",
+            "Inicio de la ejecución de la visita técnica no programada #$idVisit.",
             [[
                 'BOTON' => 'Ver detalles',
                 'FUNCION' => 'openPreventiveWorkOrderDetails',
@@ -2886,7 +3402,7 @@ class PreventiveMaintenanceController extends Controller
             'S002V01F11RVTP',
             'S002V01P01REVI',
             'Actualización',
-            "El usuario $name (" . $usr->USUA_IDUS . ") inició la visita preventiva #$idVisit.",
+            "El usuario $name (" . $usr->USUA_IDUS . ") inició la visita técnica no programada #$idVisit.",
             $idUser,
             $nowStr,
             'S002V01S02AOTR'

+ 1 - 0
sistema-mantenimiento-back/routes/api.php

@@ -200,6 +200,7 @@ Route::post("/start-preventive-visit",
 Route::post("/attend-preventive-visit",                                             "App\Http\Controllers\PreventiveMaintenanceController@attendPreventiveVisit");      //Operario acepta o rechaza invitación a visita
 Route::get("/get-visit-attendance/{idVisit}/{idUser}/{line}",                       "App\Http\Controllers\PreventiveMaintenanceController@getVisitAttendance");          //Obtiene la información de una visita preventiva
 Route::get("/get-visit-staff/{idVisit}/{idUser}/{line}",                            "App\Http\Controllers\PreventiveMaintenanceController@getVisitStaff");                //Obtiene el personal de una visita preventiva
+Route::post("/register-operator-closing-comment",                                    "App\Http\Controllers\PreventiveMaintenanceController@registerOperatorClosingComment"); //Registra comentario de finalización de operario en estado CP
 
 //Módulo contadores y activadores
 Route::get("/activator/consult/{idUser}/{line}",                                    "App\Http\Controllers\CountersActivatorsController@getActivators");