TemplatesManagementController.php 79 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Support\Facades\Validator;
  6. use Illuminate\Support\Carbon;
  7. use Illuminate\Support\Facades\Log;
  8. use Exception;
  9. use PhpOffice\PhpSpreadsheet\IOFactory;
  10. use PhpOffice\PhpSpreadsheet\Shared\Date;
  11. // Importaciones de controladores necesarios
  12. use App\Http\Controllers\ResponseController;
  13. use App\Http\Controllers\EncryptionController;
  14. use App\Http\Controllers\FunctionsController;
  15. use App\Http\Controllers\ResourcesController;
  16. use App\Http\Controllers\DocumentManagementController;
  17. class TemplatesManagementController extends Controller {
  18. private $responseController;
  19. private $encryptionController;
  20. private $functionsController;
  21. private $resourcesController;
  22. private $documentManagementController;
  23. private $templatesUbic;
  24. public function __construct(){
  25. $this->responseController = new ResponseController();
  26. $this->encryptionController = new EncryptionController();
  27. $this->functionsController = new FunctionsController();
  28. $this->resourcesController = new ResourcesController();
  29. $this->documentManagementController = new DocumentManagementController();
  30. $this->templatesUbic = app_path('Http/Controllers');
  31. }
  32. /**
  33. * Validar y procesar plantilla Excel de equipamientos
  34. * Recibe el ID del archivo temporal ya cargado
  35. */
  36. public function validateAndProcessExcelTemplate(Request $request) {
  37. DB::enableQueryLog();
  38. $validator = Validator::make($request->all(), [
  39. 'id_user' => 'required|string',
  40. 'id_file' => 'required|string',
  41. 'linea' => 'required|integer',
  42. ]);
  43. if($validator->fails()){
  44. return $this->responseController->makeResponse(
  45. true,
  46. "Se encontraron uno o más errores.",
  47. $this->responseController->makeErrors($validator->errors()->messages()),
  48. 401
  49. );
  50. }
  51. $form = $request->all();
  52. $idUser = '0000000001';
  53. if(!$idUser){
  54. return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
  55. }
  56. $usr = DB::table('S002V01TUSUA')->where([
  57. ['USUA_IDUS', '=', $idUser],
  58. ['USUA_NULI', '=', $form['linea']]
  59. ])->first();
  60. if(is_null($usr)){
  61. return $this->responseController->makeResponse(true, 'El usuario no está registrado', [], 404);
  62. }
  63. // Obtener archivo temporal
  64. $fileIdDecrypted = $this->encryptionController->decrypt($form['id_file']);
  65. $tempFile = DB::table('S002V01TARTE')->where([
  66. ['ARTE_IDAR', '=', $fileIdDecrypted],
  67. ['ARTE_NULI', '=', $form['linea']]
  68. ])->first();
  69. if(is_null($tempFile)){
  70. return $this->responseController->makeResponse(true, 'El archivo temporal no fue encontrado', [], 404);
  71. }
  72. try {
  73. // Validar estructura del Excel
  74. $structureValidation = $this->validateExcelStructure($tempFile->ARTE_UBTE);
  75. if(!$structureValidation['valid']) {
  76. return $this->responseController->makeResponse(true, $structureValidation['message'], [], 400);
  77. }
  78. // Procesar contenido del Excel
  79. $spreadsheet = IOFactory::load($tempFile->ARTE_UBTE);
  80. $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ'];
  81. $acronymMappings = $this->extractAcronymMappings($spreadsheet);
  82. $cargaMap = $acronymMappings['carga'];
  83. $lruMap = $acronymMappings['lru'];
  84. $sheetsToProcess = [
  85. 'CASO 1', 'CASO 2', 'CASO 3',
  86. 'CASO 4', 'CASO 5', 'CASO 6'
  87. ];
  88. $processedData = [];
  89. $errors = [];
  90. $successCount = 0;
  91. DB::beginTransaction();
  92. foreach($sheetsToProcess as $sheetName) {
  93. // Verificar si la hoja existe en el documento
  94. if(!in_array($sheetName, $spreadsheet->getSheetNames())) {
  95. $errors[] = "Hoja requerida no encontrada: $sheetName";
  96. continue;
  97. }
  98. // Obtener configuración de la hoja
  99. $sheetConfig = $config['worksheets'][$sheetName] ?? null;
  100. if(!$sheetConfig) {
  101. $errors[] = "Configuración no encontrada para hoja: $sheetName";
  102. continue;
  103. }
  104. $worksheet = $spreadsheet->getSheetByName($sheetName);
  105. $result = $this->processWorksheet(
  106. $worksheet,
  107. $sheetConfig,
  108. $sheetName,
  109. $form['linea'],
  110. $idUser,
  111. $cargaMap, // Nuevo parámetro
  112. $lruMap // Nuevo parámetro
  113. );
  114. $worksheet = $spreadsheet->getSheetByName($sheetName);
  115. $result = $this->processWorksheet($worksheet, $sheetConfig, $sheetName, $form['linea'], $idUser, $cargaMap, $lruMap);
  116. $processedData = array_merge($processedData, $result['data']);
  117. $errors = array_merge($errors, $result['errors']);
  118. $successCount += $result['count'];
  119. }
  120. if(!empty($errors) && empty($processedData)) {
  121. DB::rollBack();
  122. return $this->responseController->makeResponse(true, "Errores en el procesamiento: " . implode('; ', array_slice($errors, 0, 5)), [], 400);
  123. }
  124. // Insertar datos procesados en la tabla temporal de equipamientos
  125. foreach($processedData as $data) {
  126. DB::table('S002V01TPCEQ')->insert($data);
  127. }
  128. DB::commit();
  129. // Registrar actividad
  130. $nowStr = Carbon::now('America/Mexico_city')->toDateTimeString();
  131. $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
  132. $actions = DB::getQueryLog();
  133. $idac = $this->functionsController->registerActivity(
  134. $form['linea'],
  135. 'S002V01M07GEEQ',
  136. 'S002V01F01ADEQ',
  137. 'S002V01P11REEQ',
  138. 'Procesamiento',
  139. "El usuario $name (" . $usr->USUA_IDUS . ") procesó $successCount equipos desde el archivo {$tempFile->ARTE_NOAR}.",
  140. $idUser,
  141. $nowStr,
  142. );
  143. $this->functionsController->registerLog($actions, $idUser, $nowStr, $idac, $form['linea']);
  144. $responseData = [
  145. 'equipos_procesados' => $successCount,
  146. 'archivo' => $tempFile->ARTE_NOAR,
  147. 'id_archivo' => $form['id_file']
  148. ];
  149. if(!empty($errors)) {
  150. $responseData['advertencias'] = array_slice($errors, 0, 10);
  151. }
  152. return $this->responseController->makeResponse(false, "Procesamiento exitoso", $responseData);
  153. } catch(Exception $e) {
  154. DB::rollBack();
  155. return $this->responseController->makeResponse(true, "Error al procesar el archivo: " . $e->getMessage(), [], 500);
  156. }
  157. }
  158. private function extractAcronymMappings($spreadsheet) {
  159. $mappings = [
  160. 'carga' => [
  161. 'equipos' => [],
  162. 'modelos' => []
  163. ],
  164. 'lru' => [
  165. 'equipos' => [],
  166. 'modelos' => []
  167. ]
  168. ];
  169. // Normalizar nombres de hojas
  170. $sheetNames = array_map('strtolower', $spreadsheet->getSheetNames());
  171. // 1. Encontrar hoja CARGA DE EQUIPOS (case-insensitive)
  172. $cargaSheet = null;
  173. foreach ($spreadsheet->getAllSheets() as $sheet) {
  174. if (strtolower(trim($sheet->getTitle())) === 'equipamiento') {
  175. $cargaSheet = $sheet;
  176. break;
  177. }
  178. }
  179. // 2. Procesar hoja CARGA DE EQUIPOS si existe
  180. if ($cargaSheet) {
  181. $highestRow = $cargaSheet->getHighestRow();
  182. Log::debug("Procesando CARGA DE EQUIPOS. Filas: $highestRow");
  183. for ($row = 9; $row <= $highestRow; $row++) {
  184. // Mapeo de equipos
  185. $acronym = $this->getCellValue($cargaSheet, 'D', $row);
  186. $type = $this->getCellValue($cargaSheet, 'C', $row);
  187. if (!empty($acronym) && !empty($type)) {
  188. $mappings['carga']['equipos'][$acronym] = $type;
  189. }
  190. // Mapeo de modelos
  191. $modelAcronym = $this->getCellValue($cargaSheet, 'F', $row);
  192. $modelFull = $this->getCellValue($cargaSheet, 'E', $row);
  193. if (!empty($modelAcronym) && !empty($modelFull)) {
  194. $mappings['carga']['modelos'][$modelAcronym] = $modelFull;
  195. }
  196. }
  197. Log::debug('Mapeos CARGA modelos: ' . json_encode($mappings['carga']['modelos']));
  198. } else {
  199. Log::warning('Hoja CARGA DE EQUIPOS no encontrada en el archivo');
  200. }
  201. // 3. Procesar hoja LRU (usando el mismo método robusto)
  202. $lruSheet = null;
  203. foreach ($spreadsheet->getAllSheets() as $sheet) {
  204. if (strtolower(trim($sheet->getTitle())) === 'lru') {
  205. $lruSheet = $sheet;
  206. break;
  207. }
  208. }
  209. if ($lruSheet) {
  210. $highestRow = $lruSheet->getHighestRow();
  211. for ($row = 9; $row <= $highestRow; $row++) {
  212. // Mapeo de equipos
  213. $acronym = $this->getCellValue($lruSheet, 'D', $row);
  214. $type = $this->getCellValue($lruSheet, 'C', $row);
  215. if (!empty($acronym) && !empty($type)) {
  216. $mappings['lru']['equipos'][$acronym] = $type;
  217. }
  218. // Mapeo de modelos
  219. $modelAcronym = $this->getCellValue($lruSheet, 'F', $row);
  220. $modelFull = $this->getCellValue($lruSheet, 'E', $row);
  221. if (!empty($modelAcronym) && !empty($modelFull)) {
  222. $mappings['lru']['modelos'][$modelAcronym] = $modelFull;
  223. }
  224. }
  225. }
  226. return $mappings;
  227. }
  228. // Nueva función para obtener valores de celda robusta
  229. private function getCellValue($worksheet, $column, $row) {
  230. try {
  231. $cell = $worksheet->getCell($column . $row);
  232. // Manejar cualquier tipo de dato
  233. return trim($cell->getFormattedValue());
  234. } catch (\Exception $e) {
  235. Log::error("Error leyendo celda $column$row: " . $e->getMessage());
  236. return '';
  237. }
  238. }
  239. /**
  240. * Validar solo la estructura del Excel (sin procesar datos)
  241. */
  242. public function validateExcelStructureOnly(Request $request) {
  243. $validator = Validator::make($request->all(), [
  244. 'id_user' => 'required|string',
  245. 'id_file' => 'required|string',
  246. 'linea' => 'required|integer',
  247. ]);
  248. if($validator->fails()){
  249. return $this->responseController->makeResponse(
  250. true,
  251. "Se encontraron uno o más errores.",
  252. $this->responseController->makeErrors($validator->errors()->messages()),
  253. 401
  254. );
  255. }
  256. $form = $request->all();
  257. $idUser = $this->encryptionController->decrypt($form['id_user']);
  258. if(!$idUser){
  259. return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
  260. }
  261. // Obtener archivo temporal
  262. $fileIdDecrypted = $this->encryptionController->decrypt($form['id_file']);
  263. $tempFile = DB::table('S002V01TARTE')->where([
  264. ['ARTE_IDAR', '=', $fileIdDecrypted],
  265. ['ARTE_NULI', '=', $form['linea']]
  266. ])->first();
  267. if(is_null($tempFile)){
  268. return $this->responseController->makeResponse(true, 'El archivo temporal no fue encontrado', [], 404);
  269. }
  270. // Validar solo la estructura
  271. $structureValidation = $this->validateExcelStructure($tempFile->ARTE_UBTE);
  272. if(!$structureValidation['valid']) {
  273. return $this->responseController->makeResponse(true, $structureValidation['message'], [], 400);
  274. }
  275. return $this->responseController->makeResponse(false, "Estructura del archivo válida", [
  276. 'archivo' => $tempFile->ARTE_NOAR,
  277. 'validado' => true
  278. ]);
  279. }
  280. private function validateExcelStructure($filePath) {
  281. try {
  282. $spreadsheet = IOFactory::load($filePath);
  283. $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ'];
  284. $requiredSheets = array_keys($config['worksheets']);
  285. $existingSheets = $spreadsheet->getSheetNames();
  286. // Verificar que existan las hojas requeridas
  287. $missingSheets = array_diff($requiredSheets, $existingSheets);
  288. if(!empty($missingSheets)) {
  289. return [
  290. 'valid' => false,
  291. 'message' => 'Tu documento no cumple con las hojas requeridas de la plantilla'
  292. ];
  293. }
  294. // Validar headers de cada hoja
  295. foreach($config['worksheets'] as $sheetName => $sheetConfig) {
  296. if(!in_array($sheetName, $existingSheets)) continue;
  297. $worksheet = $spreadsheet->getSheetByName($sheetName);
  298. $headerValidation = $this->validateSheetHeaders($worksheet, $sheetConfig, $sheetName);
  299. if(!$headerValidation['valid']) {
  300. return [
  301. 'valid' => false,
  302. 'message' => 'Tu documento tiene error en los Headers, revísalos e intentalo de nuevo'
  303. ];
  304. }
  305. // Validar que tenga datos (solo para CARGA DE EQUIPOS)
  306. if($sheetName === 'CARGA DE EQUIPOS') {
  307. $dataValidation = $this->validateSheetHasData($worksheet, $sheetName, $sheetConfig);
  308. if(!$dataValidation['valid']) {
  309. return $dataValidation;
  310. }
  311. }
  312. }
  313. return ['valid' => true, 'message' => 'Estructura válida'];
  314. } catch(Exception $e) {
  315. return [
  316. 'valid' => false,
  317. 'message' => 'Error al validar la estructura del archivo: ' . $e->getMessage()
  318. ];
  319. }
  320. }
  321. private function validateSheetHeaders($worksheet, $sheetConfig, $sheetName) {
  322. // Para CATÁLOGOS usar validación especial
  323. if($sheetName === 'CATÁLOGOS') {
  324. return $this->validateCatalogosSheet($worksheet);
  325. }
  326. $headerRow = $sheetConfig['header_row'] ?? 4;
  327. $fieldMapping = $sheetConfig['field_mapping'];
  328. // Definir headers esperados según la configuración de Angular
  329. $expectedHeaders = $this->getExpectedHeaders($sheetName);
  330. if(empty($expectedHeaders)) {
  331. return ['valid' => true, 'message' => "No hay headers específicos para validar en $sheetName"];
  332. }
  333. foreach($expectedHeaders as $column => $expectedHeader) {
  334. $cellValue = $worksheet->getCell($column . $headerRow)->getCalculatedValue();
  335. $actualHeader = $cellValue ? trim((string)$cellValue) : '';
  336. if($actualHeader !== $expectedHeader) {
  337. return [
  338. 'valid' => false,
  339. 'message' => "Error en header de $sheetName, columna $column: esperado '$expectedHeader', encontrado '$actualHeader'"
  340. ];
  341. }
  342. }
  343. return ['valid' => true, 'message' => "Headers válidos para $sheetName"];
  344. }
  345. private function validateCatalogosSheet($worksheet) {
  346. $expectedHeaders = [
  347. 'B' => 'FAMILIA',
  348. 'C' => 'ACRÓNIMO',
  349. 'E' => 'SUBFAMILIA',
  350. 'F' => 'ACRÓNIMO',
  351. 'G' => 'SUBFAMILY',
  352. 'I' => 'UBICACIONES (FRENTES)',
  353. 'J' => 'CÓDIGO',
  354. 'L' => 'RMS',
  355. 'M' => 'ELEMENTO',
  356. 'N' => 'CÓDIGO',
  357. 'P' => 'OCUPACIÓN',
  358. 'Q' => 'CÓDIGO',
  359. 'S' => 'ESTADO',
  360. 'T' => 'ACRÓNIMO'
  361. ];
  362. foreach($expectedHeaders as $column => $expectedHeader) {
  363. $cellValue = $worksheet->getCell($column . '5')->getCalculatedValue();
  364. $actualHeader = $cellValue ? trim((string)$cellValue) : '';
  365. if($actualHeader !== $expectedHeader) {
  366. return [
  367. 'valid' => false,
  368. 'message' => "Error en CATÁLOGOS, fila 5, columna $column: esperado '$expectedHeader', encontrado '$actualHeader'"
  369. ];
  370. }
  371. }
  372. return ['valid' => true, 'message' => 'Headers válidos para CATÁLOGOS'];
  373. }
  374. private function validateSheetHasData($worksheet, $sheetName, $sheetConfig) {
  375. try {
  376. $range = $worksheet->calculateWorksheetDimension();
  377. $highestRow = $worksheet->getHighestRow();
  378. $dataStartRow = $sheetConfig['date_start_row'] ?? 9;
  379. if($highestRow < $dataStartRow) {
  380. return [
  381. 'valid' => false,
  382. 'message' => "La hoja '$sheetName' no contiene datos. Se requiere al menos un registro con información."
  383. ];
  384. }
  385. // Verificar que hay al menos una fila con datos
  386. $hasData = false;
  387. $mainColumns = ['B', 'D', 'F', 'G']; // Columnas principales para verificar
  388. for($row = $dataStartRow; $row <= $highestRow; $row++) {
  389. foreach($mainColumns as $col) {
  390. $cellValue = $worksheet->getCell($col . $row)->getCalculatedValue();
  391. if($cellValue !== null && $cellValue !== '' && trim((string)$cellValue) !== '') {
  392. $hasData = true;
  393. break 2;
  394. }
  395. }
  396. }
  397. if(!$hasData) {
  398. return [
  399. 'valid' => false,
  400. 'message' => "La hoja '$sheetName' no contiene datos. Se requiere al menos un registro con información."
  401. ];
  402. }
  403. return ['valid' => true, 'message' => 'Datos encontrados'];
  404. } catch(Exception $e) {
  405. return [
  406. 'valid' => false,
  407. 'message' => "Error al validar datos en '$sheetName': " . $e->getMessage()
  408. ];
  409. }
  410. }
  411. private function getExpectedHeaders($sheetName) {
  412. $headers = [
  413. 'CARGA DE EQUIPOS' => [
  414. 'B' => 'CÓDIGO EQUIVALENTE',
  415. 'C' => 'TIPO / DESCRIPCIÓN',
  416. 'D' => 'ACRÓNIMO DEL EQUIPO',
  417. 'E' => 'MODELO COMPLETO',
  418. 'F' => 'ACRÓNIMO DEL MODELO',
  419. 'G' => 'ID',
  420. 'H' => 'NO.SERIE',
  421. 'I' => 'NO. CÓDIGO DE BARRAS',
  422. 'J' => 'CARÁCTER',
  423. 'K' => 'FECHA DE VENCIMIENTO DEL ARTÍCULO',
  424. 'L' => 'ETIQUETA FINAL DEL EQUIPO',
  425. ],
  426. 'LRU' => [
  427. 'B' => 'CÓDIGO EQUIVALENTE',
  428. 'C' => 'TIPO / DESCRIPCIÓN',
  429. 'D' => 'ACRÓNIMO DEL EQUIPO',
  430. 'E' => 'MODELO COMPLETO',
  431. 'F' => 'ACRÓNIMO DEL MODELO',
  432. 'G' => 'ID',
  433. 'H' => 'NO.SERIE',
  434. 'I' => 'NO. CÓDIGO DE BARRAS',
  435. 'J' => 'CARÁCTER',
  436. 'K' => 'FECHA DE VENCIMIENTO DEL ARTÍCULO',
  437. 'L' => 'ETIQUETA FINAL DEL EQUIPO'
  438. ],
  439. 'CASO 1'=> [
  440. 'B'=> 'LÍNEA',
  441. 'D'=> 'UBICACIÓN',
  442. 'F'=> 'NIVEL',
  443. 'H'=> 'OCUPACIÓN',
  444. 'J'=> 'ELEMENTO',
  445. 'L'=> 'COORDENADAS PLANO GENERAL',
  446. 'M'=> 'COORDENADAS DETALLE',
  447. 'N'=> 'COORDENADAS DE POSICIÓN',
  448. 'P'=> 'FAMILIA',
  449. 'R'=> 'SUBFAMILIA',
  450. 'T'=> 'ESTADO',
  451. 'V'=> 'TIPO',
  452. 'X'=> 'MODELO',
  453. 'Z'=> 'ID',
  454. 'AB'=> 'TIPO',
  455. 'AD'=> 'MODELO',
  456. 'AF'=> 'ID',
  457. 'AH'=> 'CÓDIGO COMPLETO SAM',
  458. 'AI'=> 'CÓDIGO EQUIVALENTE',
  459. ],
  460. 'CASO 2'=> [
  461. 'B'=> 'LÍNEA',
  462. 'D'=> 'UBICACIÓN',
  463. 'F'=> 'NIVEL',
  464. 'H'=> 'OCUPACIÓN',
  465. 'J'=> 'ELEMENTO',
  466. 'L'=> 'COORDENADAS PLANO GENERAL',
  467. 'M'=> 'COORDENADAS DE POSICIÓN',
  468. 'N'=> 'POSICIÓN EN RACK',
  469. 'P'=> 'FAMILIA',
  470. 'R'=> 'SUBFAMILIA',
  471. 'T'=> 'ESTADO',
  472. 'V'=> 'TIPO',
  473. 'X'=> 'MODELO',
  474. 'Z'=> 'ID',
  475. 'AB'=> 'TIPO',
  476. 'AD'=> 'MODELO',
  477. 'AF'=> 'ID',
  478. 'AH'=> 'CÓDIGO COMPLETO',
  479. 'AI'=> 'CÓDIGO EQUIVALENTE',
  480. ],
  481. 'CASO 3'=> [
  482. 'B'=> 'LÍNEA',
  483. 'D'=> 'UBICACIÓN ORIGEN',
  484. 'F'=> 'NIVEL ORIGEN',
  485. 'H'=> 'OCUPACIÓN ORIGEN',
  486. 'J'=> 'ELEMENTO ORIGEN',
  487. 'L'=> 'PK ORIGEN',
  488. 'N'=> 'UBICACIÓN DESTINO',
  489. 'P'=> 'NIVEL DESTINO',
  490. 'R'=> 'OCUPACIÓN DESTINO',
  491. 'T'=> 'ELEMENTO DESTINO',
  492. 'V'=> 'PK DESTINO',
  493. 'X'=> 'FAMILIA',
  494. 'Z'=> 'SUBFAMILIA',
  495. 'AB'=> 'ESTADO',
  496. 'AD'=> 'TIPO',
  497. 'AF'=> 'MODELO',
  498. 'AH'=> 'ID',
  499. 'AJ'=> 'TIPO',
  500. 'AL'=> 'MODELO',
  501. 'AN'=> 'ID',
  502. 'AP'=> 'CÓDIGO COMPLETO',
  503. 'AQ'=> 'CÓDIGO EQUIVALENTE'
  504. ],
  505. 'CASO 4'=> [
  506. 'B'=> 'LÍNEA',
  507. 'D'=> 'UBICACIÓN ORIGEN',
  508. 'F'=> 'NIVEL ORIGEN',
  509. 'H'=> 'OCUPACIÓN ORIGEN',
  510. 'J'=> 'ELEMENTO ORIGEN',
  511. 'L'=> 'SECUENCIAL ORIGEN',
  512. 'N'=> 'COORDENADAS PLANO',
  513. 'O'=> 'COORDENADAS DETALLE',
  514. 'P'=> 'COORDENADAS DE POSICIÓN',
  515. 'R'=> 'UBICACIÓN DESTINO',
  516. 'T'=> 'NIVEL DESTINO',
  517. 'V'=> 'OCUPACIÓN DESTINO',
  518. 'X'=> 'ELEMENTO DESTINO',
  519. 'Z'=> 'SECUENCIAL DESTINO',
  520. 'AB'=> 'COORDENADAS PLANO',
  521. 'AC'=> 'COORDENADAS DETALLE',
  522. 'AD'=> 'COORDENADAS DE POSICIÓN',
  523. 'AF'=> 'FAMILIA',
  524. 'AH'=> 'SUBFAMILIA',
  525. 'AJ'=> 'ESTADO',
  526. 'AL'=> 'TIPO',
  527. 'AN'=> 'MODELO',
  528. 'AP'=> 'ID',
  529. 'AR'=> 'TIPO',
  530. 'AT'=> 'MODELO',
  531. 'AV'=> 'ID',
  532. 'AX'=> 'CÓDIGO COMPLETO',
  533. 'AY'=> 'CÓDIGO EQUIVALENTE',
  534. ],
  535. 'CASO 5'=> [
  536. 'B'=> 'LÍNEA',
  537. 'D'=> 'UBICACIÓN',
  538. 'F'=> 'NIVEL',
  539. 'H'=> 'OCUPACIÓN',
  540. 'J'=> 'ÁREA',
  541. 'L'=> 'ELEMENTO',
  542. 'N'=> 'FAMILIA',
  543. 'P'=> 'SUBFAMILIA',
  544. 'R'=> 'ESTADO',
  545. 'T'=> 'TIPO',
  546. 'V'=> 'MODELO',
  547. 'X'=> 'ID',
  548. 'Z'=> 'TIPO',
  549. 'AB'=> 'MODELO',
  550. 'AD'=> 'ID',
  551. 'AF'=> 'CÓDIGO COMPLETO',
  552. 'AG'=> 'CÓDIGO EQUIVALENTE',
  553. ],
  554. 'CASO 6' => [
  555. 'B'=> 'LÍNEA',
  556. 'D'=> 'UBICACIÓN',
  557. 'F'=> 'NIVEL',
  558. 'H'=> 'OCUPACIÓN',
  559. 'J'=> 'ELEMENTO',
  560. 'L'=> 'POSICIÓN',
  561. 'N'=> 'FAMILIA',
  562. 'P'=> 'SUBFAMILIA',
  563. 'R'=> 'ESTADO',
  564. 'T'=> 'TIPO',
  565. 'V'=> 'MODELO',
  566. 'X'=> 'ID',
  567. 'Z'=> 'TIPO',
  568. 'AB'=> 'MODELO',
  569. 'AD'=> 'ID',
  570. 'AF'=> 'CÓDIGO COMPLETO',
  571. 'AG'=> 'CÓDIGO EQUIVALENTE'
  572. ]
  573. ];
  574. return $headers[$sheetName] ?? [];
  575. }
  576. private function extractRowData($worksheet, $row, $fieldMapping, $sheetName) {
  577. $rowData = [];
  578. foreach($fieldMapping as $column => $field) {
  579. $cellValue = $worksheet->getCell($column . $row)->getCalculatedValue();
  580. // Manejar concatenaciones especiales para coordenadas
  581. if($sheetName === 'CASO 1' || $sheetName === 'CASO 2') {
  582. if($column === 'L' && isset($fieldMapping['M']) && isset($fieldMapping['N'])) {
  583. $coordL = $worksheet->getCell('L' . $row)->getCalculatedValue();
  584. $coordM = $worksheet->getCell('M' . $row)->getCalculatedValue();
  585. $coordN = $worksheet->getCell('N' . $row)->getCalculatedValue();
  586. $rowData['PCEQ_COOR'] = $coordL . $coordM . $coordN;
  587. }
  588. }
  589. if($sheetName === 'CASO 4') {
  590. if($column === 'N' && isset($fieldMapping['O']) && isset($fieldMapping['P'])) {
  591. $coordN = $worksheet->getCell('N' . $row)->getCalculatedValue();
  592. $coordO = $worksheet->getCell('O' . $row)->getCalculatedValue();
  593. $coordP = $worksheet->getCell('P' . $row)->getCalculatedValue();
  594. $rowData['PCEQ_COOR_ORIGEN'] = $coordN . $coordO . $coordP;
  595. }
  596. if($column === 'AB' && isset($fieldMapping['AC']) && isset($fieldMapping['AD'])) {
  597. $coordAB = $worksheet->getCell('AB' . $row)->getCalculatedValue();
  598. $coordAC = $worksheet->getCell('AC' . $row)->getCalculatedValue();
  599. $coordAD = $worksheet->getCell('AD' . $row)->getCalculatedValue();
  600. $rowData['PCEQ_COOR_DESTINO'] = $coordAB . $coordAC . $coordAD;
  601. }
  602. }
  603. if(!empty($field) && !in_array($field, ['.', '-', '_', '+', ':', ';'])) {
  604. $rowData[$field] = $cellValue;
  605. }
  606. }
  607. return $rowData;
  608. }
  609. private function isEmptyRow($rowData) {
  610. $requiredFields = ['PCEQ_TIEQ', 'PCEQ_MOEQ']; // Campos mínimos
  611. foreach($requiredFields as $field) {
  612. if(isset($rowData[$field]) && !empty(trim((string)$rowData[$field]))) {
  613. return false;
  614. }
  615. }
  616. return true;
  617. }
  618. private function validateRowData($rowData, $sheetName, $row) {
  619. $errors = [];
  620. $valid = true;
  621. // Para hojas de casos 1-6
  622. if (in_array($sheetName, ['CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6'])) {
  623. $tienePrimerConjunto =
  624. !empty(trim($rowData['PCEQ_TIEQ'] ?? '')) &&
  625. !empty(trim($rowData['PCEQ_MOEQ'] ?? ''));
  626. $tieneSegundoConjunto =
  627. !empty(trim($rowData['PCEQ_TIEQ_HIJO'] ?? '')) &&
  628. !empty(trim($rowData['PCEQ_MOEQ_HIJO'] ?? ''));
  629. if (!$tienePrimerConjunto && !$tieneSegundoConjunto) {
  630. $errors[] = 'Se requiere al menos un conjunto completo de datos (Tipo/Modelo)';
  631. $valid = false;
  632. }
  633. } else {
  634. // Validación original para otras hojas
  635. if(empty($rowData['PCEQ_TIEQ'] ?? '')) {
  636. $errors[] = 'Tipo de equipo requerido';
  637. $valid = false;
  638. }
  639. if(empty($rowData['PCEQ_MOEQ'] ?? '')) {
  640. $errors[] = 'Modelo requerido';
  641. $valid = false;
  642. }
  643. }
  644. // Validar fechas si existen
  645. $dateFields = ['PCEQ_FEAD', 'PCEQ_FIGA', 'PCEQ_FTGA'];
  646. foreach($dateFields as $dateField) {
  647. if(isset($rowData[$dateField]) && !empty($rowData[$dateField])) {
  648. try {
  649. Carbon::parse($rowData[$dateField]);
  650. } catch(Exception $e) {
  651. $errors[] = "Fecha inválida en campo $dateField";
  652. $valid = false;
  653. }
  654. }
  655. }
  656. return ['valid' => $valid, 'errors' => $errors];
  657. }
  658. private function noDate($value)
  659. {
  660. if (empty($value) ||
  661. strtoupper(trim($value)) === 'NA' ||
  662. trim($value) === '.' ||
  663. trim($value) === '-') {
  664. return now()->format('Y-m-d');
  665. }
  666. if (is_numeric($value)) {
  667. try {
  668. return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value)->format('Y-m-d');
  669. } catch (Exception $e) {
  670. return null;
  671. }
  672. }
  673. if (is_string($value)) {
  674. try {
  675. $date = new \DateTime($value);
  676. return $date->format('Y-m-d');
  677. } catch (Exception $e) {
  678. return null;
  679. }
  680. }
  681. return null;
  682. }
  683. private function prepareEquipmentData($rowData, $linea, $idUser, $sheetName) {
  684. $nowStr = Carbon::now('America/Mexico_city')->toDateTimeString();
  685. // si se llegan a meter columnas de fechas a la hoja ests variables formatean la fecha de la columna, nada más habría que formatear
  686. // queda así
  687. //'PCEQ_FIGA' => $fechaInicioGarantia 'PCEQ_FEAD' => $fechaAdquisicion 'PCEQ_FTGA' => $fechaFinGarantia
  688. $caracter = $this->normalizeCaracter($rowData['PCEQ_CARA'] ?? null);
  689. $fechaInicioGarantia = $this->parseDate($rowData['PCEQ_FIGA'] ?? null);
  690. $fechaFinGarantia = $this->parseDate($rowData['PCEQ_FTGA'] ?? null);
  691. $fechaAdquisicion = $this->parseDate($rowData['PCEQ_FEAD'] ?? null);
  692. $fechaVencimiento = $this->noDate($rowData['PCEQ_FVAR'] ?? null);
  693. $equipmentData = [
  694. 'PCEQ_FIGA' => now()->format('Y-m-d'),
  695. 'PCEQ_FEAD' => now()->format('Y-m-d'),
  696. 'PCEQ_FTGA' => now()->format('Y-m-d'),
  697. 'PCEQ_OTCO' => '[]',
  698. 'PCEQ_NULI' => $linea,
  699. 'PCEQ_UBOR' => $rowData['PCEQ_UBOR'] ?? '',
  700. 'PCEQ_NIOR' => $rowData['PCEQ_NIOR'] ?? '',
  701. 'PCEQ_OCOR' => $rowData['PCEQ_OCOR'] ?? '',
  702. 'PCEQ_ELOR' => $rowData['PCEQ_ELOR'] ?? '',
  703. 'PCEQ_COOR' => $rowData['PCEQ_COOR'] ?? '',
  704. 'PCEQ_FAMI' => $rowData['PCEQ_FAMI'] ?? '',
  705. 'PCEQ_SUBF' => $rowData['PCEQ_SUBF'] ?? '',
  706. 'PCEQ_ESEQ' => $rowData['PCEQ_ESEQ'] ?? 'A',
  707. 'PCEQ_TIEQ' => $rowData['PCEQ_TIEQ'] ?? '',
  708. 'PCEQ_MOEQ' => $rowData['PCEQ_MOEQ'] ?? '',
  709. 'PCEQ_CPGE' => $rowData['PCEQ_CPGE'] ?? '',
  710. 'PCEQ_TICO' => $this->getCodeTypeFromSheet($sheetName),
  711. 'PCEQ_JERA' => 'Padre',
  712. 'PCEQ_EQPA' => null,
  713. 'PCEQ_NUSE' => $rowData['PCEQ_NUSE'] ?? '',
  714. 'PCEQ_COBA' => $rowData['PCEQ_COBA'] ?? '',
  715. 'PCEQ_CARA' => $caracter,
  716. 'PCEQ_PREQ' => $rowData['PCEQ_PREQ'] ?? 0,
  717. 'PCEQ_FVAR' => $fechaVencimiento,
  718. 'PCEQ_GAIM' => json_encode([]),
  719. 'PCEQ_DORE' => json_encode([]),
  720. 'PCEQ_ESRE' => 'Revisión',
  721. 'PCEQ_USRE' => $idUser,
  722. 'PCEQ_FERE' => $nowStr,
  723. 'PCEQ_IDPR' => $this->generateNumericUniqueId($linea)
  724. ];
  725. $this->handleHierarchy($equipmentData, $rowData, $sheetName);
  726. // Lógica de jerarquía (Padre/Hijo) para casos 1-6
  727. if (in_array($sheetName, ['CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6'])) {
  728. // Verificar si los campos del segundo conjunto (AB y AD) tienen datos
  729. $tieneSegundoConjunto =
  730. !empty(trim($rowData['PCEQ_TIEQ_HIJO'] ?? '')) &&
  731. !empty(trim($rowData['PCEQ_MOEQ_HIJO'] ?? ''));
  732. if ($tieneSegundoConjunto) {
  733. // Usar valores del segundo conjunto (Hijo)
  734. $equipmentData['PCEQ_TIEQ'] = $rowData['PCEQ_TIEQ_HIJO'];
  735. $equipmentData['PCEQ_MOEQ'] = $rowData['PCEQ_MOEQ_HIJO'];
  736. $equipmentData['PCEQ_JERA'] = 'Hijo';
  737. } else {
  738. // Usar valores del primer conjunto (Padre)
  739. $equipmentData['PCEQ_TIEQ'] = $rowData['PCEQ_TIEQ'] ?? '';
  740. $equipmentData['PCEQ_MOEQ'] = $rowData['PCEQ_MOEQ'] ?? '';
  741. $equipmentData['PCEQ_JERA'] = 'Padre';
  742. }
  743. } else {
  744. // Para otras hojas (CARGA DE EQUIPOS, LRU) usar valores normales
  745. $equipmentData['PCEQ_TIEQ'] = $rowData['PCEQ_TIEQ'] ?? '';
  746. $equipmentData['PCEQ_MOEQ'] = $rowData['PCEQ_MOEQ'] ?? '';
  747. }
  748. // Agregar campos específicos según el tipo de hoja
  749. $this->addSheetSpecificFields($equipmentData, $rowData, $sheetName);
  750. // Manejar fechas
  751. if(isset($rowData['PCEQ_FEAD'])) {
  752. $equipmentData['PCEQ_FEAD'] = $this->noDate($rowData['PCEQ_FEAD']) ?? now()->format('Y-m-d');
  753. }
  754. if(isset($rowData['PCEQ_FIGA'])) {
  755. $equipmentData['PCEQ_FIGA'] = $this->noDate($rowData['PCEQ_FIGA']) ?? now()->format('Y-m-d');
  756. }
  757. if(isset($rowData['PCEQ_FTGA'])) {
  758. $equipmentData['PCEQ_FTGA'] = $this->noDate($rowData['PCEQ_FTGA']) ?? now()->format('Y-m-d');
  759. }
  760. if(isset($rowData['PCEQ_FVAR'])) {
  761. $equipmentData['PCEQ_FVAR'] = $this->noDate($rowData['PCEQ_FVAR']) ?? now()->format('Y-m-d');
  762. }
  763. // GENERAR PCEQ_IDPR: Solo un ID numérico único simple
  764. $equipmentData['PCEQ_IDPR'] = $this->generateNumericUniqueId($linea);
  765. // GENERAR PCEQ_CPGE: El código concatenado completo (solo para CASOS 1-6)
  766. if(in_array($sheetName, ['CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6'])) {
  767. $equipmentData['PCEQ_CPGE'] = $this->generateConcatenatedCode($equipmentData, $sheetName);
  768. } else {
  769. // Para CARGA DE EQUIPOS y LRU, usar valor por defecto o del Excel
  770. $equipmentData['PCEQ_CPGE'] = $rowData['PCEQ_CPGE'] ?? 'DEFAULT_CODE_' . $equipmentData['PCEQ_IDPR'];
  771. }
  772. return $equipmentData;
  773. }
  774. private function parseDate($value) {
  775. if (!$value) return null;
  776. try {
  777. // Intentar como fecha de Excel
  778. if (is_numeric($value)) {
  779. return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value)
  780. ->format('Y-m-d');
  781. }
  782. // Intentar como cadena de fecha
  783. return Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d');
  784. } catch (\Exception $e) {
  785. return null;
  786. }
  787. }
  788. // Función para manejar jerarquía
  789. private function handleHierarchy(&$equipmentData, $rowData, $sheetName) {
  790. if (!in_array($sheetName, ['CASO 1','CASO 2','CASO 3','CASO 4','CASO 5','CASO 6'])) {
  791. return;
  792. }
  793. // Determinar si es hijo
  794. $isHijo = !empty($rowData['PCEQ_TIEQ_HIJO']) || !empty($rowData['PCEQ_MOEQ_HIJO']);
  795. if ($isHijo) {
  796. $equipmentData['PCEQ_JERA'] = 'Hijo';
  797. $equipmentData['PCEQ_TIEQ'] = $rowData['PCEQ_TIEQ_HIJO'] ?? '';
  798. $equipmentData['PCEQ_MOEQ'] = $rowData['PCEQ_MOEQ_HIJO'] ?? '';
  799. }
  800. }
  801. private function normalizeCaracter($value) {
  802. if (!$value) return null;
  803. $value = strtoupper(trim($value));
  804. $mapping = [
  805. 'REPARABLE' => 'REPARABLE',
  806. 'REP' => 'REPARABLE',
  807. 'R' => 'REPARABLE',
  808. 'CONSUMIBLE' => 'CONSUMIBLE',
  809. 'CONSUM' => 'CONSUMIBLE',
  810. 'CONS' => 'CONSUMIBLE',
  811. 'C' => 'CONSUMIBLE',
  812. 'DESECHABLE' => 'CONSUMIBLE'
  813. ];
  814. return $mapping[$value] ?? null;
  815. }
  816. // MÉTODO PARA GENERAR PCEQ_IDPR: Solo ID numérico único
  817. private function generateNumericUniqueId($linea) {
  818. // Obtener el máximo ID existente para esta línea
  819. $maxId = DB::table('S002V01TPCEQ')
  820. ->where('PCEQ_NULI', $linea)
  821. ->max('PCEQ_IDPR');
  822. // Generar el siguiente ID secuencial
  823. $nextId = $maxId ? $maxId + 1 : 1;
  824. // Verificar que el ID no exista (por seguridad)
  825. while(DB::table('S002V01TPCEQ')
  826. ->where('PCEQ_NULI', $linea)
  827. ->where('PCEQ_IDPR', $nextId)
  828. ->exists()) {
  829. $nextId++;
  830. }
  831. return $nextId;
  832. }
  833. // MÉTODO PARA GENERAR PCEQ_CPGE: El código concatenado complejo (solo CASOS 1-6)
  834. private function generateConcatenatedCode($equipmentData, $sheetName) {
  835. // Solo generar concatenación para CASOS 1-6
  836. switch($sheetName) {
  837. case 'CASO 1':
  838. case 'CASO 2':
  839. return $this->generateCodeCaso1y2($equipmentData);
  840. case 'CASO 3':
  841. return $this->generateCodeCaso3($equipmentData);
  842. case 'CASO 4':
  843. return $this->generateCodeCaso4($equipmentData);
  844. case 'CASO 5':
  845. case 'CASO 6':
  846. return $this->generateCodeCaso5y6($equipmentData);
  847. default:
  848. return 'DEFAULT_CODE_' . $equipmentData['PCEQ_IDPR'];
  849. }
  850. }
  851. private function generateCodeCaso1y2($data) {
  852. // Patrón: LINEA.UBICACION.NIVEL.OCUPACION.ELEMENTO+COORDENADAS_FAMILIA.SUBFAMILIA.ESTADO.TIPO-MODELO-ID.TIPO-MODELO-ID-CODIGO_SAM
  853. $parts = [
  854. str_pad($data['PCEQ_NULI'], 2, '0', STR_PAD_LEFT), // Línea con padding
  855. $data['PCEQ_UBOR'] ?? '', // Ubicación
  856. $data['PCEQ_NIOR'] ?? '', // Nivel
  857. $data['PCEQ_OCOR'] ?? '', // Ocupación
  858. $data['PCEQ_ELOR'] ?? '', // Elemento
  859. $data['PCEQ_COOR'] ?? '', // Coordenadas
  860. $data['PCEQ_FAMI'] ?? '', // Familia
  861. '',
  862. $data['PCEQ_SUBF'] ?? '', // Subfamilia
  863. $data['PCEQ_ESEQ'] ?? 'A', // Estado
  864. $data['PCEQ_TIEQ'] ?? '', // Tipo
  865. $data['PCEQ_MOEQ'] ?? '', // Modelo
  866. $data['PCEQ_IDPR'], // ID numérico
  867. ];
  868. return implode('.', array_filter($parts));
  869. }
  870. private function generateCodeCaso3($data) {
  871. // Patrón para CASO 3: LINEA.UBICACION_ORIGEN.NIVEL_ORIGEN.OCUPACION_ORIGEN.ELEMENTO_ORIGEN:UBICACION_DESTINO.NIVEL_DESTINO.OCUPACION_DESTINO.ELEMENTO_DESTINO-FAMILIA-SUBFAMILIA.ESTADO-TIPO-MODELO-ID
  872. $parts = [
  873. str_pad($data['PCEQ_NULI'], 2, '0', STR_PAD_LEFT),
  874. $data['PCEQ_UBOR'] ?? '', // Ubicación origen
  875. $data['PCEQ_NIDE'] ?? '', // Nivel origen
  876. $data['PCEQ_OCDE'] ?? '', // Ocupación origen
  877. $data['PCEQ_ELDE'] ?? '', // Elemento origen
  878. $data['PCEQ_UBDE'] ?? '', // Ubicación destino
  879. $data['PCEQ_FAMI'] ?? '', // Familia
  880. $data['PCEQ_SUBF'] ?? '', // Subfamilia
  881. $data['PCEQ_ESEQ'] ?? 'A', // Estado
  882. $data['PCEQ_TIEQ'] ?? '', // Tipo
  883. $data['PCEQ_MOEQ'] ?? '', // Modelo
  884. $data['PCEQ_IDPR'], // ID numérico
  885. ];
  886. return implode('.', array_filter($parts));
  887. }
  888. private function generateCodeCaso4($data) {
  889. // Patrón para CASO 4: Similar a CASO 3 pero con secuenciales y coordenadas adicionales
  890. $parts = [
  891. str_pad($data['PCEQ_NULI'], 2, '0', STR_PAD_LEFT),
  892. $data['PCEQ_UBOR'] ?? '', // Ubicación origen
  893. $data['PCEQ_SEOR'] ?? '0', // Secuencial origen
  894. $data['PCEQ_COOR'] ?? '', // Coordenadas origen
  895. $data['PCEQ_UBDE'] ?? '', // Ubicación destino
  896. $data['PCEQ_SEDE'] ?? '0', // Secuencial destino
  897. $data['PCEQ_CODE'] ?? '', // Coordenadas destino
  898. $data['PCEQ_FAMI'] ?? '', // Familia
  899. $data['PCEQ_SUBF'] ?? '', // Subfamilia
  900. $data['PCEQ_ESEQ'] ?? 'A', // Estado
  901. $data['PCEQ_TIEQ'] ?? '', // Tipo
  902. $data['PCEQ_MOEQ'] ?? '', // Modelo
  903. $data['PCEQ_IDPR'], // ID numérico
  904. ];
  905. return implode('.', array_filter($parts));
  906. }
  907. private function generateCodeCaso5y6($data) {
  908. // Patrón para CASO 5 y 6: LINEA.UBICACION.NIVEL.OCUPACION.AREA.ELEMENTO_FAMILIA.SUBFAMILIA.ESTADO.TIPO-MODELO-ID.TIPO-MODELO-ID-CODIGO
  909. $parts = [
  910. str_pad($data['PCEQ_NULI'], 2, '0', STR_PAD_LEFT),
  911. $data['PCEQ_UBOR'] ?? '', // Ubicación
  912. $data['PCEQ_NIOR'] ?? '', // Nivel
  913. $data['PCEQ_OCOR'] ?? '', // Ocupación
  914. $data['PCEQ_ARTR'] ?? '', // Área
  915. $data['PCEQ_ELOR'] ?? '', // Elemento
  916. $data['PCEQ_FAMI'] ?? '', // Familia
  917. $data['PCEQ_SUBF'] ?? '', // Subfamilia
  918. $data['PCEQ_ESEQ'] ?? 'A', // Estado
  919. $data['PCEQ_TIEQ'] ?? '', // Tipo
  920. $data['PCEQ_MOEQ'] ?? '', // Modelo
  921. $data['PCEQ_IDPR'], // ID numérico
  922. ];
  923. return implode('.', array_filter($parts));
  924. }
  925. // MÉTODO ALTERNATIVO: Usar timestamp + hash para garantizar unicidad (para CARGA DE EQUIPOS y LRU)
  926. private function generateUniqueHashId($linea) {
  927. // Crear ID único basado en timestamp + random para CARGA DE EQUIPOS y LRU
  928. return $linea . '_' . date('YmdHis') . '_' . uniqid();
  929. }
  930. // MÉTODO DE RESPALDO: Si falla todo, usar este ID de emergencia
  931. private function generateFallbackId($linea) {
  932. return 'FALLBACK_' . $linea . '_' . microtime(true) . '_' . rand(10000, 99999);
  933. }
  934. // SOLUCIÓN MEJORADA: Verificar duplicados de PCEQ_IDPR antes de insertar
  935. private function insertEquipmentWithDuplicateHandling($equipmentData) {
  936. $maxAttempts = 5;
  937. $attempt = 0;
  938. while($attempt < $maxAttempts) {
  939. try {
  940. // Verificar si ya existe este PCEQ_IDPR para esta línea
  941. $exists = DB::table('S002V01TPCEQ')
  942. ->where('PCEQ_NULI', $equipmentData['PCEQ_NULI'])
  943. ->where('PCEQ_IDPR', $equipmentData['PCEQ_IDPR'])
  944. ->exists();
  945. if($exists) {
  946. // Regenerar PCEQ_IDPR numérico único
  947. $equipmentData['PCEQ_IDPR'] = $this->generateNumericUniqueId($equipmentData['PCEQ_NULI']);
  948. // Si es un CASO 1-6, regenerar también el PCEQ_CPGE con el nuevo IDPR
  949. if(isset($equipmentData['PCEQ_CPGE']) && strpos($equipmentData['PCEQ_CPGE'], '.') !== false) {
  950. $sheetName = $this->getSheetNameFromCodeType($equipmentData['PCEQ_TICO']);
  951. if($sheetName) {
  952. $equipmentData['PCEQ_CPGE'] = $this->generateConcatenatedCode($equipmentData, $sheetName);
  953. }
  954. }
  955. $attempt++;
  956. continue;
  957. }
  958. // Intentar insertar
  959. return DB::table('S002V01TPCEQ')->insert($equipmentData);
  960. } catch(\Illuminate\Database\QueryException $e) {
  961. // Si es error de clave duplicada, regenerar ID
  962. if($e->getCode() == 23000 || strpos($e->getMessage(), 'Duplicate entry') !== false) {
  963. $equipmentData['PCEQ_IDPR'] = $this->generateNumericUniqueId($equipmentData['PCEQ_NULI']);
  964. $attempt++;
  965. continue;
  966. }
  967. // Si es otro tipo de error, re-lanzar
  968. throw $e;
  969. }
  970. }
  971. throw new Exception("No se pudo insertar el registro después de $maxAttempts intentos");
  972. }
  973. // MÉTODO AUXILIAR: Obtener nombre de hoja basado en tipo de código
  974. private function getSheetNameFromCodeType($codeType) {
  975. $codeTypes = [
  976. '1' => 'CASO 1',
  977. '2' => 'CASO 2',
  978. '3' => 'CASO 3',
  979. '4' => 'CASO 4',
  980. '5' => 'CASO 5',
  981. '6' => 'CASO 6'
  982. ];
  983. return $codeTypes[$codeType] ?? null;
  984. }
  985. // SOLUCIÓN 3: Usar upsert (insertar o actualizar)
  986. private function upsertEquipmentData($equipmentData) {
  987. return DB::table('S002V01TPCEQ')->updateOrInsert(
  988. [
  989. 'PCEQ_NULI' => $equipmentData['PCEQ_NULI'],
  990. 'PCEQ_TICO' => $equipmentData['PCEQ_TICO'],
  991. 'PCEQ_IDPR' => $equipmentData['PCEQ_IDPR']
  992. ],
  993. $equipmentData
  994. );
  995. }
  996. // MÉTODO MODIFICADO PARA PROCESAR WORKSHEET CON MANEJO DE DUPLICADOS
  997. private function processWorksheet($worksheet, $sheetConfig, $sheetName, $linea, $idUser, $cargaMap, $lruMap) {
  998. $processedData = [];
  999. $errors = [];
  1000. $count = 0;
  1001. $highestRow = $worksheet->getHighestRow();
  1002. $startRow = $sheetConfig['date_start_row'];
  1003. $fieldMapping = $sheetConfig['field_mapping'];
  1004. // Obtener todos los equipos de CARGA y LRU para búsqueda
  1005. $cargaEquipments = $this->getAllCargaEquipments($worksheet->getParent());
  1006. $lruEquipments = $this->getAllLruEquipments($worksheet->getParent());
  1007. for($row = $startRow; $row <= $highestRow; $row++) {
  1008. $rowData = $this->extractRowData($worksheet, $row, $fieldMapping, $sheetName);
  1009. if($this->isEmptyRow($rowData)) {
  1010. continue;
  1011. }
  1012. $this->replaceAcronyms($rowData, $sheetName, $cargaMap, $lruMap);
  1013. $validation = $this->validateRowData($rowData, $sheetName, $row);
  1014. if(!$validation['valid']) {
  1015. $errors[] = "Hoja: $sheetName, Fila: $row - " . implode(', ', $validation['errors']);
  1016. continue;
  1017. }
  1018. // Determinar si es equipo padre o hijo
  1019. $isHijo = !empty($rowData['PCEQ_TIEQ_HIJO']) || !empty($rowData['PCEQ_MOEQ_HIJO']);
  1020. // Buscar equipo completo en CARGA o LRU
  1021. $fullEquipment = null;
  1022. if($isHijo) {
  1023. // Buscar en LRU por tipo y modelo hijo
  1024. $fullEquipment = $this->findEquipment(
  1025. $lruEquipments,
  1026. $rowData['PCEQ_TIEQ_HIJO'] ?? '',
  1027. $rowData['PCEQ_MOEQ_HIJO'] ?? ''
  1028. );
  1029. } else {
  1030. // Buscar en CARGA por tipo y modelo padre
  1031. $fullEquipment = $this->findEquipment(
  1032. $cargaEquipments,
  1033. $rowData['PCEQ_TIEQ'] ?? '',
  1034. $rowData['PCEQ_MOEQ'] ?? ''
  1035. );
  1036. }
  1037. // Combinar datos del caso con datos del equipo completo
  1038. if($fullEquipment) {
  1039. $rowData = array_merge($rowData, $fullEquipment);
  1040. }
  1041. $equipmentData = $this->prepareEquipmentData($rowData, $linea, $idUser, $sheetName);
  1042. $processedData[] = $equipmentData;
  1043. $count++;
  1044. }
  1045. return [
  1046. 'data' => $processedData,
  1047. 'errors' => $errors,
  1048. 'count' => $count
  1049. ];
  1050. }
  1051. // Obtener todos los equipos de CARGA DE EQUIPOS
  1052. private function getAllCargaEquipments($spreadsheet) {
  1053. $equipments = [];
  1054. $sheet = $spreadsheet->getSheetByName('CARGA DE EQUIPOS');
  1055. if(!$sheet) return $equipments;
  1056. $highestRow = $sheet->getHighestRow();
  1057. $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ']['worksheets']['CARGA DE EQUIPOS'];
  1058. $startRow = $config['date_start_row'];
  1059. for($row = $startRow; $row <= $highestRow; $row++) {
  1060. $equipment = $this->extractRowData($sheet, $row, $config['field_mapping'], 'CARGA DE EQUIPOS');
  1061. if(!$this->isEmptyRow($equipment)) {
  1062. $equipments[] = $equipment;
  1063. }
  1064. }
  1065. return $equipments;
  1066. }
  1067. // Obtener todos los equipos de LRU
  1068. private function getAllLruEquipments($spreadsheet) {
  1069. $equipments = [];
  1070. $sheet = $spreadsheet->getSheetByName('LRU');
  1071. if(!$sheet) return $equipments;
  1072. $highestRow = $sheet->getHighestRow();
  1073. $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ']['worksheets']['LRU'];
  1074. $startRow = $config['date_start_row'];
  1075. for($row = $startRow; $row <= $highestRow; $row++) {
  1076. $equipment = $this->extractRowData($sheet, $row, $config['field_mapping'], 'LRU');
  1077. if(!$this->isEmptyRow($equipment)) {
  1078. $equipments[] = $equipment;
  1079. }
  1080. }
  1081. return $equipments;
  1082. }
  1083. // Buscar equipo por tipo y modelo
  1084. private function findEquipment($equipments, $tipo, $modelo) {
  1085. $normalize = function($value) {
  1086. return trim(strtoupper($value));
  1087. };
  1088. $tipo = $normalize($tipo);
  1089. $modelo = $normalize($modelo);
  1090. foreach($equipments as $equipment) {
  1091. $eqTipo = $normalize($equipment['PCEQ_TIEQ'] ?? '');
  1092. $eqModelo = $normalize($equipment['PCEQ_MOEQ'] ?? '');
  1093. if($eqTipo === $tipo && $eqModelo === $modelo) {
  1094. return $equipment;
  1095. }
  1096. }
  1097. return null;
  1098. }
  1099. private function replaceAcronyms(&$rowData, $sheetName, $cargaMap, $lruMap) {
  1100. // Solo aplica para hojas de casos
  1101. if (!in_array($sheetName, ['CASO 1','CASO 2','CASO 3','CASO 4','CASO 5','CASO 6'])) {
  1102. return;
  1103. }
  1104. // Reemplazar acrónimo padre (de CARGA DE EQUIPOS)
  1105. if (isset($rowData['PCEQ_TIEQ'])) {
  1106. $acronym = trim($rowData['PCEQ_TIEQ']);
  1107. if (isset($cargaMap['equipos'][$acronym])) {
  1108. $rowData['PCEQ_TIEQ'] = $cargaMap['equipos'][$acronym];
  1109. }
  1110. }
  1111. // Reemplazar acrónimo hijo (de LRU)
  1112. if (isset($rowData['PCEQ_TIEQ_HIJO'])) {
  1113. $acronym = trim($rowData['PCEQ_TIEQ_HIJO']);
  1114. if (isset($lruMap['equipos'][$acronym])) {
  1115. $rowData['PCEQ_TIEQ_HIJO'] = $lruMap['equipos'][$acronym];
  1116. }
  1117. }
  1118. if (isset($rowData['PCEQ_MOEQ'])) {
  1119. $acronym = trim($rowData['PCEQ_MOEQ']);
  1120. if (isset($cargaMap['modelos'][$acronym])) {
  1121. $rowData['PCEQ_MOEQ'] = $cargaMap['modelos'][$acronym];
  1122. }
  1123. }
  1124. // NUEVO: Reemplazar acrónimo de modelo hijo (de LRU)
  1125. if (isset($rowData['PCEQ_MOEQ_HIJO'])) {
  1126. $acronym = trim($rowData['PCEQ_MOEQ_HIJO']);
  1127. if (isset($lruMap['modelos'][$acronym])) {
  1128. $rowData['PCEQ_MOEQ_HIJO'] = $lruMap['modelos'][$acronym];
  1129. }
  1130. }
  1131. }
  1132. // MÉTODO ALTERNATIVO: Limpiar datos duplicados antes del procesamiento
  1133. public function cleanDuplicateRecords(Request $request) {
  1134. $validator = Validator::make($request->all(), [
  1135. 'id_user' => 'required|string',
  1136. 'linea' => 'required|integer',
  1137. ]);
  1138. if($validator->fails()) {
  1139. return $this->responseController->makeResponse(
  1140. true,
  1141. "Se encontraron uno o más errores.",
  1142. $this->responseController->makeErrors($validator->errors()->messages()),
  1143. 401
  1144. );
  1145. }
  1146. $form = $request->all();
  1147. $idUser = $this->encryptionController->decrypt($form['id_user']);
  1148. if(!$idUser) {
  1149. return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
  1150. }
  1151. try {
  1152. DB::beginTransaction();
  1153. // Eliminar registros duplicados manteniendo solo el más reciente
  1154. $duplicatesDeleted = DB::statement("
  1155. DELETE p1 FROM S002V01TPCEQ p1
  1156. INNER JOIN S002V01TPCEQ p2
  1157. WHERE p1.PCEQ_NULI = p2.PCEQ_NULI
  1158. AND p1.PCEQ_TICO = p2.PCEQ_TICO
  1159. AND p1.PCEQ_IDPR = p2.PCEQ_IDPR
  1160. AND p1.PCEQ_FERE < p2.PCEQ_FERE
  1161. AND p1.PCEQ_NULI = ?
  1162. ", [$form['linea']]);
  1163. DB::commit();
  1164. return $this->responseController->makeResponse(false, "Duplicados eliminados exitosamente", [
  1165. 'linea' => $form['linea'],
  1166. 'duplicados_eliminados' => $duplicatesDeleted
  1167. ]);
  1168. } catch(Exception $e) {
  1169. DB::rollBack();
  1170. return $this->responseController->makeResponse(true, "Error al limpiar duplicados: " . $e->getMessage(), [], 500);
  1171. }
  1172. }
  1173. private function getCodeTypeFromSheet($sheetName) {
  1174. $codeTypes = [
  1175. 'CASO 1' => '1',
  1176. 'CASO 2' => '2',
  1177. 'CASO 3' => '3',
  1178. 'CASO 4' => '4',
  1179. 'CASO 5' => '5',
  1180. 'CASO 6' => '6',
  1181. ];
  1182. return $codeTypes[$sheetName] ?? '1';
  1183. }
  1184. private function addSheetSpecificFields(&$equipmentData, $rowData, $sheetName) {
  1185. switch($sheetName) {
  1186. case 'CASO 1':
  1187. case 'CASO 2':
  1188. $equipmentData['PCEQ_UBOR'] = $rowData['PCEQ_UBOR'] ?? '';
  1189. $equipmentData['PCEQ_NIOR'] = $rowData['PCEQ_NIOR'] ?? '';
  1190. $equipmentData['PCEQ_OCOR'] = $rowData['PCEQ_OCOR'] ?? '';
  1191. $equipmentData['PCEQ_ELOR'] = $rowData['PCEQ_ELOR'] ?? '';
  1192. $equipmentData['PCEQ_COOR'] = $rowData['PCEQ_COOR'] ?? '';
  1193. $equipmentData['PCEQ_FAMI'] = $rowData['PCEQ_FAMI'] ?? '';
  1194. $equipmentData['PCEQ_SUBF'] = $rowData['PCEQ_SUBF'] ?? '';
  1195. if(isset($rowData['PCEQ_CPGE'])) {
  1196. $equipmentData['PCEQ_CPGE'] = $rowData['PCEQ_CPGE'];
  1197. }
  1198. break;
  1199. case 'CASO 3':
  1200. $equipmentData['PCEQ_UBOR'] = $rowData['PCEQ_UBOR'] ?? '';
  1201. $equipmentData['PCEQ_UBDE'] = $rowData['PCEQ_UBDE'] ?? '';
  1202. if(isset($rowData['PCEQ_IDPR'])) {
  1203. $equipmentData['PCEQ_IDPR'] = $rowData['PCEQ_IDPR'];
  1204. }
  1205. if(isset($rowData['PCEQ_CPGE'])) {
  1206. $equipmentData['PCEQ_CPGE'] = $rowData['PCEQ_CPGE'];
  1207. }
  1208. break;
  1209. case 'CASO 4':
  1210. $equipmentData['PCEQ_UBOR'] = $rowData['PCEQ_UBOR'] ?? '';
  1211. $equipmentData['PCEQ_UBDE'] = $rowData['PCEQ_UBDE'] ?? '';
  1212. $equipmentData['PCEQ_SEOR'] = $rowData['PCEQ_SEOR'] ?? 0;
  1213. $equipmentData['PCEQ_SEDE'] = $rowData['PCEQ_SEDE'] ?? 0;
  1214. $equipmentData['PCEQ_COOR'] = $rowData['PCEQ_COOR_ORIGEN'] ?? '';
  1215. $equipmentData['PCEQ_CODE'] = $rowData['PCEQ_COOR_DESTINO'] ?? '';
  1216. if(isset($rowData['PCEQ_IDPR'])) {
  1217. $equipmentData['PCEQ_IDPR'] = $rowData['PCEQ_IDPR'];
  1218. }
  1219. if(isset($rowData['PCEQ_CPGE'])) {
  1220. $equipmentData['PCEQ_CPGE'] = $rowData['PCEQ_CPGE'];
  1221. }
  1222. break;
  1223. case 'CASO 5':
  1224. case 'CASO 6':
  1225. $equipmentData['PCEQ_UBOR'] = $rowData['PCEQ_UBOR'] ?? '';
  1226. $equipmentData['PCEQ_NIOR'] = $rowData['PCEQ_NIOR'] ?? '';
  1227. $equipmentData['PCEQ_OCOR'] = $rowData['PCEQ_OCOR'] ?? '';
  1228. $equipmentData['PCEQ_ARTR'] = $rowData['PCEQ_ARTR'] ?? '';
  1229. $equipmentData['PCEQ_ELOR'] = $rowData['PCEQ_ELOR'] ?? '';
  1230. if(isset($rowData['PCEQ_IDPR'])) {
  1231. $equipmentData['PCEQ_IDPR'] = $rowData['PCEQ_IDPR'];
  1232. }
  1233. if(isset($rowData['PCEQ_CPGE'])) {
  1234. $equipmentData['PCEQ_CPGE'] = $rowData['PCEQ_CPGE'];
  1235. }
  1236. break;
  1237. }
  1238. }
  1239. // Método para obtener equipos en revisión
  1240. public function getPendingEquipments(Request $request) {
  1241. $validator = Validator::make($request->all(), [
  1242. 'id_user' => 'required|string',
  1243. 'linea' => 'required|integer',
  1244. ]);
  1245. if($validator->fails()) {
  1246. return $this->responseController->makeResponse(
  1247. true,
  1248. "Se encontraron uno o más errores.",
  1249. $this->responseController->makeErrors($validator->errors()->messages()),
  1250. 401
  1251. );
  1252. }
  1253. $form = $request->all();
  1254. $idUser = $this->encryptionController->decrypt($form['id_user']);
  1255. if(!$idUser) {
  1256. return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
  1257. }
  1258. $pendingEquipments = DB::table('S002V01TPCEQ')
  1259. ->where('PCEQ_NULI', $form['linea'])
  1260. ->where('PCEQ_ESRE', 'Revisión')
  1261. ->orderBy('PCEQ_FERE', 'desc')
  1262. ->get();
  1263. $equipmentsArray = [];
  1264. foreach($pendingEquipments as $equipment) {
  1265. $equipmentsArray[] = [
  1266. 'id' => $this->encryptionController->encrypt($equipment->PCEQ_IDPR),
  1267. 'codigo' => $equipment->PCEQ_CPGE,
  1268. 'tipo' => $equipment->PCEQ_TIEQ,
  1269. 'modelo' => $equipment->PCEQ_MOEQ,
  1270. 'familia' => $equipment->PCEQ_FAMI,
  1271. 'subfamilia' => $equipment->PCEQ_SUBF,
  1272. 'estado' => $equipment->PCEQ_ESEQ,
  1273. 'fecha_registro' => $equipment->PCEQ_FERE,
  1274. 'estado_revision' => $equipment->PCEQ_ESRE
  1275. ];
  1276. }
  1277. return $this->responseController->makeResponse(false, 'EXITO.', $equipmentsArray);
  1278. }
  1279. /**
  1280. * Método para aprobar equipamientos desde la tabla temporal hacia la tabla final
  1281. */
  1282. public function approveEquipments(Request $request) {
  1283. DB::enableQueryLog();
  1284. $validator = Validator::make($request->all(), [
  1285. 'id_user' => 'required|string',
  1286. 'linea' => 'required|integer',
  1287. 'equipment_ids' => 'required|array',
  1288. 'equipment_ids.*' => 'required|string'
  1289. ]);
  1290. if($validator->fails()) {
  1291. return $this->responseController->makeResponse(
  1292. true,
  1293. "Se encontraron uno o más errores.",
  1294. $this->responseController->makeErrors($validator->errors()->messages()),
  1295. 401
  1296. );
  1297. }
  1298. $form = $request->all();
  1299. $idUser = $this->encryptionController->decrypt($form['id_user']);
  1300. if(!$idUser) {
  1301. return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
  1302. }
  1303. $usr = DB::table('S002V01TUSUA')->where([
  1304. ['USUA_IDUS', '=', $idUser],
  1305. ['USUA_NULI', '=', $form['linea']]
  1306. ])->first();
  1307. if(is_null($usr)) {
  1308. return $this->responseController->makeResponse(true, 'El usuario no está registrado', [], 404);
  1309. }
  1310. try {
  1311. DB::beginTransaction();
  1312. $approvedCount = 0;
  1313. $errors = [];
  1314. foreach($form['equipment_ids'] as $encryptedId) {
  1315. $equipmentId = $this->encryptionController->decrypt($encryptedId);
  1316. if(!$equipmentId) {
  1317. $errors[] = "ID de equipamiento inválido: $encryptedId";
  1318. continue;
  1319. }
  1320. // Obtener equipamiento temporal
  1321. $tempEquipment = DB::table('S002V01TPCEQ')
  1322. ->where('PCEQ_IDPR', $equipmentId)
  1323. ->where('PCEQ_NULI', $form['linea'])
  1324. ->where('PCEQ_ESRE', 'Revisión')
  1325. ->first();
  1326. if(!$tempEquipment) {
  1327. $errors[] = "Equipamiento no encontrado o ya procesado: $equipmentId";
  1328. continue;
  1329. }
  1330. // Mover a tabla final de equipamientos
  1331. $finalEquipmentData = $this->prepareFinalEquipmentData($tempEquipment, $idUser);
  1332. $finalEquipmentId = DB::table('S002V01TEQUI')->insertGetId($finalEquipmentData);
  1333. // Actualizar estado en tabla temporal
  1334. DB::table('S002V01TPCEQ')
  1335. ->where('PCEQ_IDPR', $equipmentId)
  1336. ->update([
  1337. 'PCEQ_ESRE' => 'Aprobado',
  1338. 'PCEQ_USAP' => $idUser,
  1339. 'PCEQ_FEAP' => Carbon::now('America/Mexico_city')->toDateTimeString()
  1340. ]);
  1341. $approvedCount++;
  1342. }
  1343. if($approvedCount === 0) {
  1344. DB::rollBack();
  1345. return $this->responseController->makeResponse(true, "No se pudo aprobar ningún equipamiento: " . implode('; ', $errors), [], 400);
  1346. }
  1347. DB::commit();
  1348. // Registrar actividad
  1349. $nowStr = Carbon::now('America/Mexico_city')->toDateTimeString();
  1350. $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
  1351. $actions = DB::getQueryLog();
  1352. $idac = $this->functionsController->registerActivity(
  1353. $form['linea'],
  1354. 'S002V01M07GEEQ',
  1355. 'S002V01F01ADEQ',
  1356. 'S002V01P11REEQ',
  1357. 'Aprobación',
  1358. "El usuario $name (" . $usr->USUA_IDUS . ") aprobó $approvedCount equipamientos.",
  1359. $idUser,
  1360. $nowStr,
  1361. );
  1362. $this->functionsController->registerLog($actions, $idUser, $nowStr, $idac, $form['linea']);
  1363. $responseData = [
  1364. 'equipos_aprobados' => $approvedCount,
  1365. 'total_procesados' => count($form['equipment_ids'])
  1366. ];
  1367. if(!empty($errors)) {
  1368. $responseData['errores'] = $errors;
  1369. }
  1370. return $this->responseController->makeResponse(false, "Aprobación exitosa", $responseData);
  1371. } catch(Exception $e) {
  1372. DB::rollBack();
  1373. return $this->responseController->makeResponse(true, "Error al aprobar equipamientos: " . $e->getMessage(), [], 500);
  1374. }
  1375. }
  1376. private function prepareFinalEquipmentData($tempEquipment, $approvingUserId) {
  1377. $nowStr = Carbon::now('America/Mexico_city')->toDateTimeString();
  1378. return [
  1379. 'EQUI_NULI' => $tempEquipment->PCEQ_NULI,
  1380. 'EQUI_COEQ' => $tempEquipment->PCEQ_CPGE,
  1381. 'EQUI_JERA' => $tempEquipment->PCEQ_JERA,
  1382. 'EQUI_EQPA' => $tempEquipment->PCEQ_EQPA,
  1383. 'EQUI_TICO' => $tempEquipment->PCEQ_TICO,
  1384. 'EQUI_UBOR' => $tempEquipment->PCEQ_UBOR,
  1385. 'EQUI_NIOR' => $tempEquipment->PCEQ_NIOR,
  1386. 'EQUI_OCOR' => $tempEquipment->PCEQ_OCOR,
  1387. 'EQUI_ELOR' => $tempEquipment->PCEQ_ELOR,
  1388. 'EQUI_FAMI' => $tempEquipment->PCEQ_FAMI,
  1389. 'EQUI_SUBF' => $tempEquipment->PCEQ_SUBF,
  1390. 'EQUI_ESEQ' => $tempEquipment->PCEQ_ESEQ,
  1391. 'EQUI_TIEQ' => $tempEquipment->PCEQ_TIEQ,
  1392. 'EQUI_MOEQ' => $tempEquipment->PCEQ_MOEQ,
  1393. 'EQUI_FEAD' => $tempEquipment->PCEQ_FEAD,
  1394. 'EQUI_FIGA' => $tempEquipment->PCEQ_FIGA,
  1395. 'EQUI_FTGA' => $tempEquipment->PCEQ_FTGA,
  1396. 'EQUI_PREQ' => $tempEquipment->PCEQ_PREQ,
  1397. 'EQUI_NUSE' => $tempEquipment->PCEQ_NUSE,
  1398. 'EQUI_GAIM' => $tempEquipment->PCEQ_GAIM,
  1399. 'EQUI_DORE' => $tempEquipment->PCEQ_DORE,
  1400. 'EQUI_USRE' => $approvingUserId,
  1401. 'EQUI_FERE' => $nowStr,
  1402. // Campos adicionales según el tipo de equipamiento
  1403. 'EQUI_UBDE' => $tempEquipment->PCEQ_UBDE ?? null,
  1404. 'EQUI_NIDE' => $tempEquipment->PCEQ_NIDE ?? null,
  1405. 'EQUI_OCDE' => $tempEquipment->PCEQ_OCDE ?? null,
  1406. 'EQUI_ELDE' => $tempEquipment->PCEQ_ELDE ?? null,
  1407. 'EQUI_COOR' => $tempEquipment->PCEQ_COOR ?? null,
  1408. 'EQUI_SEOR' => $tempEquipment->PCEQ_SEOR ?? null,
  1409. 'EQUI_SEDE' => $tempEquipment->PCEQ_SEDE ?? null,
  1410. 'EQUI_ARTR' => $tempEquipment->PCEQ_ARTR ?? null,
  1411. ];
  1412. }
  1413. /**
  1414. * Método para rechazar equipamientos
  1415. */
  1416. public function rejectEquipments(Request $request) {
  1417. $validator = Validator::make($request->all(), [
  1418. 'id_user' => 'required|string',
  1419. 'linea' => 'required|integer',
  1420. 'equipment_ids' => 'required|array',
  1421. 'equipment_ids.*' => 'required|string',
  1422. 'rejection_reason' => 'required|string'
  1423. ]);
  1424. if($validator->fails()) {
  1425. return $this->responseController->makeResponse(
  1426. true,
  1427. "Se encontraron uno o más errores.",
  1428. $this->responseController->makeErrors($validator->errors()->messages()),
  1429. 401
  1430. );
  1431. }
  1432. $form = $request->all();
  1433. $idUser = $this->encryptionController->decrypt($form['id_user']);
  1434. if(!$idUser) {
  1435. return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
  1436. }
  1437. try {
  1438. DB::beginTransaction();
  1439. $rejectedCount = 0;
  1440. foreach($form['equipment_ids'] as $encryptedId) {
  1441. $equipmentId = $this->encryptionController->decrypt($encryptedId);
  1442. if($equipmentId) {
  1443. DB::table('S002V01TPCEQ')
  1444. ->where('PCEQ_IDPR', $equipmentId)
  1445. ->where('PCEQ_NULI', $form['linea'])
  1446. ->update([
  1447. 'PCEQ_ESRE' => 'Rechazado',
  1448. 'PCEQ_MORE' => $form['rejection_reason'],
  1449. 'PCEQ_USRE' => $idUser,
  1450. 'PCEQ_FERE' => Carbon::now('America/Mexico_city')->toDateTimeString()
  1451. ]);
  1452. $rejectedCount++;
  1453. }
  1454. }
  1455. DB::commit();
  1456. return $this->responseController->makeResponse(false, "Equipamientos rechazados exitosamente", [
  1457. 'equipos_rechazados' => $rejectedCount
  1458. ]);
  1459. } catch(Exception $e) {
  1460. DB::rollBack();
  1461. return $this->responseController->makeResponse(true, "Error al rechazar equipamientos: " . $e->getMessage(), [], 500);
  1462. }
  1463. }
  1464. }
  1465. class ExcelTemplateConfig {
  1466. public static function getTemplateConfigs() {
  1467. return [
  1468. 'TPCEQ' => [
  1469. 'model' => 'S002V01TPCEQ',
  1470. 'worksheets' => [
  1471. 'EQUIPAMIENTO' => [
  1472. 'table_start' => 'B9',
  1473. 'table_end' => 'P9',
  1474. 'header_row' => 7,
  1475. 'date_start_row' => 9,
  1476. 'field_mapping' => [
  1477. 'A' => '',
  1478. 'B' => 'PCEQ_OTCO', // Código Equivalente
  1479. 'C' => 'PCEQ_TIEQ', // Tipo / Descripción
  1480. 'D' => '', // Acrónimo del equipo
  1481. 'E' => 'PCEQ_MOEQ', // Acrónimo del modelo
  1482. 'F' => '', // Acrónimo del modelo
  1483. 'G' => '', // Id
  1484. 'H' => 'PCEQ_NUSE', // No. código de barras
  1485. 'I' => 'PCEQ_COBA', // Carácter
  1486. 'J' => 'PCEQ_CARA', // Proveedor del equipamiento
  1487. 'K' => 'PCEQ_FVAR', // Fecha inicio de garantía
  1488. 'L' => '', // Fecha de vencimiento del artículo
  1489. ]
  1490. ],
  1491. 'LRU' => [
  1492. 'table_start' => 'B9',
  1493. 'table_end' => 'L9',
  1494. 'header_row' => 7,
  1495. 'date_start_row' => 9,
  1496. 'field_mapping' => [
  1497. 'A' => '',
  1498. 'B' => 'PCEQ_TIEQ', // Tipo / Descripción
  1499. 'C' => '', // Acrónimo del equipo
  1500. 'D' => 'PCEQ_MOEQ', // Modelo completo
  1501. 'E' => '', // Acrónimo del modelo
  1502. 'F' => '', // Id
  1503. 'G' => 'PCEQ_NUSE', // No. serie
  1504. 'H' => 'PCEQ_COBA', // No. código de barras
  1505. 'I' => 'PCEQ_CARA', // Carácter
  1506. 'J' => 'PCEQ_FTGA', // Fecha de vencimiento del artículo
  1507. 'K' => '', // Etiqueta final del equipo
  1508. 'L' => '', // Código equivalente
  1509. ]
  1510. ],
  1511. 'CASO 1' => [
  1512. 'table_start' => 'B8',
  1513. 'table_end' => 'AI8',
  1514. 'header_row' => 7,
  1515. 'date_start_row' => 8,
  1516. 'field_mapping' => [
  1517. 'A' => '',
  1518. 'B' => 'PCEQ_NULI', // Línea
  1519. 'C' => '.', // .
  1520. 'D' => 'PCEQ_UBOR', // Ubicación
  1521. 'E' => '.', // .
  1522. 'F' => 'PCEQ_NIOR', // Nivel
  1523. 'G' => '.', // .
  1524. 'H' => 'PCEQ_OCOR', // Ocupación
  1525. 'I' => '.', // .
  1526. 'J' => 'PCEQ_ELOR', // Elemento
  1527. 'K' => '+', // +
  1528. 'L' => '', // Coordenadas plano
  1529. 'M' => '', // Coordenadas detalle
  1530. 'N' => '', // Coordenadas de posición
  1531. 'O' => '_', // _
  1532. 'P' => 'PCEQ_FAMI', // Familia
  1533. 'Q' => '.', // .
  1534. 'R' => 'PCEQ_SUBF', // Subfamilia
  1535. 'S' => '.', // .
  1536. 'T' => 'PCEQ_ESEQ', // Estado
  1537. 'U' => '.', // .
  1538. 'V' => 'PCEQ_TIEQ', // Tipo
  1539. 'W' => '-', // -
  1540. 'X' => 'PCEQ_MOEQ', // Modelo
  1541. 'Y' => '-', // -
  1542. 'Z' => '', // ID
  1543. 'AA' => '.', // .
  1544. 'AB' => 'PCEQ_TIEQ_HIJO', // Tipo
  1545. 'AC' => '-', // -
  1546. 'AD' => 'PCEQ_MOEQ_HIJO', // Modelo
  1547. 'AE' => '-', // -
  1548. 'AF' => '', // ID
  1549. 'AG' => '', // Vacío
  1550. 'AH' => 'PCEQ_CPGE', // Código completo SAM
  1551. 'AI' => 'PCEQ_OTCO', // Código equivalente
  1552. ]
  1553. ],
  1554. 'CASO 2' => [
  1555. 'table_start' => 'B8',
  1556. 'table_end' => 'AI8',
  1557. 'header_row' => 7,
  1558. 'date_start_row' => 8,
  1559. 'field_mapping' => [
  1560. 'A' => '',
  1561. 'B' => 'PCEQ_NULI', // Línea
  1562. 'C' => '.', // .
  1563. 'D' => 'PCEQ_UBOR', // Ubicación
  1564. 'E' => '.', // .
  1565. 'F' => 'PCEQ_NIOR', // Nivel
  1566. 'G' => '.', // .
  1567. 'H' => 'PCEQ_OCOR', // Ocupación
  1568. 'I' => '.', // .
  1569. 'J' => 'PCEQ_ELOR', // Elemento
  1570. 'K' => '+', // +
  1571. 'L' => '', // Coordenadas plano
  1572. 'M' => '', // Coordenadas detalle
  1573. 'N' => '', // Coordenadas de posición
  1574. 'O' => '_', // _
  1575. 'P' => 'PCEQ_FAMI', // Familia
  1576. 'Q' => '.', // .
  1577. 'R' => 'PCEQ_SUBF', // Subfamilia
  1578. 'S' => '.', // .
  1579. 'T' => 'PCEQ_ESEQ', // Estado
  1580. 'U' => '.', // .
  1581. 'V' => 'PCEQ_TIEQ', // Tipo
  1582. 'W' => '-', // -
  1583. 'X' => 'PCEQ_MOEQ', // Modelo
  1584. 'Y' => '-', // -
  1585. 'Z' => '', // ID
  1586. 'AA' => '.', // .
  1587. 'AB' => 'PCEQ_TIEQ_HIJO', // Tipo
  1588. 'AC' => '-', // -
  1589. 'AD' => 'PCEQ_MOEQ_HIJO', // Modelo
  1590. 'AE' => '-', // -
  1591. 'AF' => '', // ID
  1592. 'AG' => '', // Vacío
  1593. 'AH' => 'PCEQ_CPGE', // Código completo SAM
  1594. 'AI' => 'PCEQ_OTCO', // Código equivalente
  1595. ]
  1596. ],
  1597. 'CASO 3' => [
  1598. 'table_start' => 'B8',
  1599. 'table_end' => 'AQ8',
  1600. 'header_row' => 7,
  1601. 'date_start_row' => 8,
  1602. 'field_mapping' => [
  1603. 'A' => '',
  1604. 'B' => 'PCEQ_NULI', // Línea
  1605. 'C' => '.', // .
  1606. 'D' => 'PCEQ_UBOR', // Ubicación origen
  1607. 'E' => '.', // .
  1608. 'F' => 'PCEQ_NIOR', // Nivel origen
  1609. 'G' => '.', // .
  1610. 'H' => 'PCEQ_OCOR', // Ocupación origen
  1611. 'I' => '.', // .
  1612. 'J' => 'PCEQ_ELOR', // Elemento origen
  1613. 'K' => '.', // .
  1614. 'L' => 'PCEQ_KIOR', // PK origen
  1615. 'M' => ':', // :
  1616. 'N' => 'PCEQ_UBDE', // Ubicación destino
  1617. 'O' => '.', // .
  1618. 'P' => 'PCEQ_NIDE', // Nivel destino
  1619. 'Q' => '.', // .
  1620. 'R' => 'PCEQ_OCDE', // Ocupación destino
  1621. 'S' => '.', // .
  1622. 'T' => 'PCEQ_ELDE', // Elemento destino
  1623. 'U' => '.', // .
  1624. 'V' => 'PCEQ_KIDE', // PK destino
  1625. 'W' => '-', // -
  1626. 'X' => 'PCEQ_FAMI', // Familia
  1627. 'Y' => '-', // -
  1628. 'Z' => 'PCEQ_SUBF', // Subfamilia
  1629. 'AA' => '.', // .
  1630. 'AB' => 'PCEQ_ESEQ', // Estado
  1631. 'AC' => '-', // -
  1632. 'AD' => 'PCEQ_TIEQ', // Tipo
  1633. 'AE' => '-', // -
  1634. 'AF' => 'PCEQ_MOEQ', // Modelo
  1635. 'AG' => '-', // -
  1636. 'AH' => '', // ID
  1637. 'AI' => '.', // .
  1638. 'AJ' => 'PCEQ_TIEQ_HIJO', // Tipo
  1639. 'AK' => '-', // -
  1640. 'AL' => 'PCEQ_MOEQ_HIJO', // Modelo
  1641. 'AM' => '-', // -
  1642. 'AN' => 'PCEQ_IDPR', // ID
  1643. 'AO' => '-', // -
  1644. 'AP' => 'PCEQ_CPGE', // Código completo
  1645. 'AQ' => 'PCEQ_OTCO', // Código equivalente
  1646. ]
  1647. ],
  1648. 'CASO 4' => [
  1649. 'table_start' => 'B8',
  1650. 'table_end' => 'AY8',
  1651. 'header_row' => 7,
  1652. 'date_start_row' => 8,
  1653. 'field_mapping' => [
  1654. 'A' => '',
  1655. 'B' => 'PCEQ_NULI', // Línea
  1656. 'C' => '.', // .
  1657. 'D' => 'PCEQ_UBOR', // Ubicación origen
  1658. 'E' => '.', // .
  1659. 'F' => 'PCEQ_NIOR', // Nivel origen
  1660. 'G' => '.', // .
  1661. 'H' => 'PCEQ_OCOR', // Ocupación origen
  1662. 'I' => '.', // .
  1663. 'J' => 'PCEQ_ELOR', // Elemento origen
  1664. 'K' => '.', // .
  1665. 'L' => 'PCEQ_SEOR', // Secuencial origen
  1666. 'M' => '.', // .
  1667. 'N' => '', // Coordenadas plano (concatenar con O,P)
  1668. 'O' => '', // Coordenadas detalle
  1669. 'P' => '', // Coordenadas de posición
  1670. 'Q' => ':', // :
  1671. 'R' => 'PCEQ_UBDE', // Ubicación destino
  1672. 'S' => '.', // .
  1673. 'T' => 'PCEQ_NIDE', // Nivel destino
  1674. 'U' => '.', // .
  1675. 'V' => 'PCEQ_OCDE', // Ocupación destino
  1676. 'W' => '.', // .
  1677. 'X' => 'PCEQ_ELDE', // Elemento destino
  1678. 'Y' => '.', // .
  1679. 'Z' => 'PCEQ_SEDE', // Secuencial destino
  1680. 'AA' => '+', // +
  1681. 'AB' => '', // Coordenadas plano (concatenar con AC,AD)
  1682. 'AC' => '', // Coordenadas detalle
  1683. 'AD' => '', // Coordenadas de posición
  1684. 'AE' => '_', // _
  1685. 'AF' => 'PCEQ_FAMI', // Familia
  1686. 'AG' => '.', // .
  1687. 'AH' => 'PCEQ_SUBF', // Subfamilia
  1688. 'AI' => '.', // .
  1689. 'AJ' => 'PCEQ_ESEQ', // Estado
  1690. 'AK' => '.', // .
  1691. 'AL' => 'PCEQ_TIEQ', // Tipo
  1692. 'AM' => '-', // -
  1693. 'AN' => 'PCEQ_MOEQ', // Modelo
  1694. 'AO' => '-', // -
  1695. 'AP' => '', // ID
  1696. 'AQ' => '.', // .
  1697. 'AR' => 'PCEQ_TIEQ_HIJO', // Tipo
  1698. 'AS' => '-', // -
  1699. 'AT' => 'PCEQ_MOEQ_HIJO', // Modelo
  1700. 'AU' => '-', // -
  1701. 'AV' => '', // ID
  1702. 'AW' => '', // Vacío
  1703. 'AX' => 'PCEQ_CPGE', // Código completo
  1704. 'AY' => 'PCEQ_OTCO', // Código equivalente
  1705. ]
  1706. ],
  1707. 'CASO 5' => [
  1708. 'table_start' => 'B8',
  1709. 'table_end' => 'AG8',
  1710. 'header_row' => 7,
  1711. 'date_start_row' => 8,
  1712. 'field_mapping' => [
  1713. 'A' => '',
  1714. 'B' => 'PCEQ_NULI', // Línea
  1715. 'C' => '.', // .
  1716. 'D' => 'PCEQ_UBOR', // Ubicación
  1717. 'E' => '.', // .
  1718. 'F' => 'PCEQ_NIOR', // Nivel
  1719. 'G' => '.', // .
  1720. 'H' => 'PCEQ_OCOR', // Ocupación
  1721. 'I' => '.', // .
  1722. 'J' => 'PCEQ_ARTR', // Área
  1723. 'K' => '.', // .
  1724. 'L' => 'PCEQ_ELOR', // Elemento
  1725. 'M' => '_', // _
  1726. 'N' => 'PCEQ_FAMI', // Familia
  1727. 'O' => '.', // .
  1728. 'P' => 'PCEQ_SUBF', // Subfamilia
  1729. 'Q' => '.', // .
  1730. 'R' => 'PCEQ_ESEQ', // Estado
  1731. 'S' => '.', // .
  1732. 'T' => 'PCEQ_TIEQ', // Tipo
  1733. 'U' => '-', // -
  1734. 'V' => 'PCEQ_MOEQ', // Modelo
  1735. 'W' => '-', // -
  1736. 'X' => '', // ID
  1737. 'Y' => '.', // .
  1738. 'Z' => 'PCEQ_TIEQ_HIJO', // Tipo LRU
  1739. 'AA' => '-', // -
  1740. 'AB' => 'PCEQ_MOEQ_HIJO', // Modelo LRU
  1741. 'AC' => '-', // -
  1742. 'AD' => '', // ID LRU
  1743. 'AE' => '', // Vacío
  1744. 'AF' => 'PCEQ_CPGE', // Código completo
  1745. 'AG' => 'PCEQ_OTCO', // Código equivalente
  1746. ]
  1747. ],
  1748. 'CASO 6' => [
  1749. 'table_start' => '',
  1750. 'table_end' => '',
  1751. 'header_row' => 7,
  1752. 'date_start_row' => 8,
  1753. 'field_mapping' => [
  1754. 'A' => '', // Vacío
  1755. 'B' => 'PCEQ_NULI', // Linea
  1756. 'C' => '.', // .
  1757. 'D' => 'PCEQ_UBOR', // Ubicación
  1758. 'E' => '.', // .
  1759. 'F' => 'PCEQ_NIOR', // Nivel
  1760. 'G' => '.', // .
  1761. 'H' => 'PCEQ_OCOR', // Ocupación
  1762. 'I' => '.', // .
  1763. 'J' => 'PCEQ_ELOR', // Elemento
  1764. 'K' => '.', // .
  1765. 'L' => 'PCEQ_COOR', // Posición
  1766. 'M' => '_', // _
  1767. 'N' => 'PCEQ_FAMI', // Familia
  1768. 'O' => '.', // .
  1769. 'P' => 'PCEQ_SUBF', // Subfamilia
  1770. 'Q' => '.', // .
  1771. 'R' => 'PCEQ_ESEQ', // Estado
  1772. 'S' => '.', // .
  1773. 'T' => 'PCEQ_TIEQ', // Tipo
  1774. 'U' => '-', // -
  1775. 'V' => 'PCEQ_MOEQ', // Modelo
  1776. 'W' => '-', // -
  1777. 'X' => 'PCEQ_IDPR', // ID
  1778. 'Y' => '.', // .
  1779. 'Z' => 'PCEQ_TIEQ_HIJO', // Tipo
  1780. 'AA' => '-', // -
  1781. 'AB' => 'PCEQ_MOEQ_HIJO', // Modelo
  1782. 'AC' => '-', // -
  1783. 'AD' => 'PCEQ_LRID', // ID
  1784. 'AE' => '', // Vacío
  1785. 'AF' => 'PCEQ_CPGE', // Código completo
  1786. 'AG' => 'PCEQ_OTCO', // Código equivalente
  1787. ]
  1788. ]
  1789. ]
  1790. ],
  1791. ];
  1792. }
  1793. };