tion [AbortError]: This operation was aborted at new DOMException (node:internal/per_context/domexception:76:18) at AbortController.abort (node:internal/abort_controller:504:18) at /home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:2808:28516 at DFt.invoke (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:81562) at t.fire (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:82334) at sHe.cancel (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:83276) at NFt.cancel (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:83672) at _de.cancelRequest (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4916:2208) at _de.getFirstMatchingRequest (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4916:1960) at /home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4954:8774 at process.processTicksAndRejections (node:internal/process/task_queues:103:5)] 2026-03-27 13:03:51.125 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:03:51.125 [info] request done: requestId: [cd6fcd62-b6bd-40e2-bc64-a7d73276a2f3] model deployment ID: [] 2026-03-27 13:03:51.359 [info] [fetchCompletions] Request 759ee8ba-db62-4241-bdd7-e7fb67ee5507 at finished with 200 status after 354.0468479997944ms 2026-03-27 13:03:51.434 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:03:51.437 [info] ccreq:ebc5324b.copilotmd | markdown 2026-03-27 13:03:51.631 [info] ccreq:b87f0804.copilotmd | success | nes-callisto-003 | 792ms | [XtabProvider] 2026-03-27 13:03:51.641 [info] ccreq:11cb730c.copilotmd | markdown 2026-03-27 13:03:53.513 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:03:53.514 [info] request done: requestId: [4be5698e-c350-45ac-a335-b6ab3c9c4751] model deployment ID: [] 2026-03-27 13:03:53.516 [info] ccreq:3a90a4c4.copilotmd | success | nes-callisto-003 | 229ms | [XtabProvider] 2026-03-27 13:03:53.520 [info] ccreq:c74db359.copilotmd | markdown 2026-03-27 13:03:59.965 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:03:59.965 [info] request done: requestId: [da558e62-8e72-4a7f-8ce8-7d5a853cbe79] model deployment ID: [] 2026-03-27 13:03:59.968 [info] ccreq:d5515f61.copilotmd | success | nes-callisto-003 | 299ms | [XtabProvider] 2026-03-27 13:03:59.976 [info] ccreq:757e1790.copilotmd | markdown 2026-03-27 13:04:00.169 [info] [fetchCompletions] Request 196d057e-0d8d-4771-a13d-9a381de901f8 at finished with 200 status after 384.2440410000272ms 2026-03-27 13:04:00.381 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:00.382 [info] request done: requestId: [9438734c-e594-40bc-8c6b-d5e60ddb746a] model deployment ID: [] 2026-03-27 13:04:00.384 [info] ccreq:b432aacc.copilotmd | success | nes-callisto-003 | 312ms | [XtabProvider] 2026-03-27 13:04:00.387 [info] ccreq:9788be42.copilotmd | markdown 2026-03-27 13:04:00.743 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:00.743 [info] request done: requestId: [925ef16c-e3f6-435a-8469-282ceb18db53] model deployment ID: [] 2026-03-27 13:04:00.748 [info] ccreq:1feb2d05.copilotmd | success | nes-callisto-003 | 247ms | [XtabProvider] 2026-03-27 13:04:00.751 [info] ccreq:8edcf25a.copilotmd | markdown 2026-03-27 13:04:00.966 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:00.983 [info] [fetchCompletions] Request 5c550186-9538-4ace-90bc-9797b9eeb522 at finished with 200 status after 367.9879189999774ms 2026-03-27 13:04:01.323 [info] ccreq:ad11fc04.copilotmd | markdown 2026-03-27 13:04:01.398 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:01.399 [info] request done: requestId: [eb2904dd-a65a-4797-93b1-a9f2831c4ca2] model deployment ID: [] 2026-03-27 13:04:01.403 [info] ccreq:ab726171.copilotmd | success | nes-callisto-003 | 347ms | [XtabProvider] 2026-03-27 13:04:01.406 [info] ccreq:91680b74.copilotmd | markdown 2026-03-27 13:04:01.554 [info] ccreq:d983cf9a.copilotmd | markdown 2026-03-27 13:04:01.564 [info] ccreq:98f70ace.copilotmd | markdown 2026-03-27 13:04:01.567 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:02.135 [info] [fetchCompletions] Request 16649064-3a95-4134-9033-b1e6f68a256e at finished with 200 status after 248.65188600006513ms 2026-03-27 13:04:02.220 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:02.221 [info] request done: requestId: [3b8fd347-f017-46fe-8f11-a878034a239e] model deployment ID: [] 2026-03-27 13:04:02.223 [info] ccreq:54e45af1.copilotmd | success | nes-callisto-003 | 246ms | [XtabProvider] 2026-03-27 13:04:02.226 [info] ccreq:bff295bc.copilotmd | markdown 2026-03-27 13:04:02.467 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:02.475 [info] ccreq:d930ce20.copilotmd | markdown 2026-03-27 13:04:02.861 [info] [fetchCompletions] Request e9204672-31f0-474b-8a6d-88306f77e94f at finished with 200 status after 319.750015999889ms 2026-03-27 13:04:02.962 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:04.606 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:04.606 [info] request done: requestId: [58a5db5b-0b35-4748-81d4-00e740542095] model deployment ID: [] 2026-03-27 13:04:04.612 [info] ccreq:51dc85dd.copilotmd | success | nes-callisto-003 | 230ms | [XtabProvider] 2026-03-27 13:04:04.619 [info] ccreq:268b27fc.copilotmd | markdown 2026-03-27 13:04:04.621 [info] [fetchCompletions] Request 81593503-4ffd-48b1-9fc6-00ed3b8ee5c9 at finished with 200 status after 328.7006920001004ms 2026-03-27 13:04:04.938 [info] [fetchCompletions] Request b99df2c2-d0e6-46eb-bc39-ab09b74c924f at finished with 200 status after 276.9297110000625ms 2026-03-27 13:04:04.951 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:04.952 [info] request done: requestId: [ae74ed06-7b39-40e8-b679-8ffa384a483f] model deployment ID: [] 2026-03-27 13:04:04.954 [info] ccreq:6a5d25dc.copilotmd | success | nes-callisto-003 | 239ms | [XtabProvider] 2026-03-27 13:04:04.957 [info] ccreq:800a86e0.copilotmd | markdown 2026-03-27 13:04:05.650 [info] [fetchCompletions] Request 2db37931-1df7-481f-8dca-91ff783133be at finished with 200 status after 282.96885299985297ms 2026-03-27 13:04:05.660 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:05.661 [info] request done: requestId: [fe5b18f2-59c9-4eb7-aecc-a0d0ad3ae4e7] model deployment ID: [] 2026-03-27 13:04:05.663 [info] ccreq:bfae9f2a.copilotmd | success | nes-callisto-003 | 259ms | [XtabProvider] 2026-03-27 13:04:05.666 [info] ccreq:2e7f0300.copilotmd | markdown 2026-03-27 13:04:05.999 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:06.055 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:06.055 [info] request done: requestId: [247fbf2c-58df-405b-bfc5-b1cc008a6510] model deployment ID: [] 2026-03-27 13:04:06.058 [info] ccreq:e803021b.copilotmd | success | nes-callisto-003 | 236ms | [XtabProvider] 2026-03-27 13:04:06.066 [info] ccreq:934d0fa4.copilotmd | markdown 2026-03-27 13:04:06.066 [info] ccreq:4580a9b0.copilotmd | markdown 2026-03-27 13:04:06.289 [info] ccreq:d80358b0.copilotmd | markdown 2026-03-27 13:04:06.412 [info] ccreq:64472695.copilotmd | markdown 2026-03-27 13:04:06.423 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:06.424 [info] request done: requestId: [13b80fa1-c0dc-44b8-9ac4-cbb6c6ca49cf] model deployment ID: [] 2026-03-27 13:04:06.428 [info] ccreq:59ad8f91.copilotmd | success | nes-callisto-003 | 240ms | [XtabProvider] 2026-03-27 13:04:06.435 [info] ccreq:ceb03b33.copilotmd | markdown 2026-03-27 13:04:07.177 [info] [fetchCompletions] Request f76233ac-c25e-4beb-a068-b37ab95f62f1 at finished with 200 status after 287.8303280000109ms 2026-03-27 13:04:07.224 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:07.224 [info] request done: requestId: [71fdf598-975b-447f-88ca-be78a4d2c656] model deployment ID: [] 2026-03-27 13:04:07.229 [info] ccreq:a04cd7c7.copilotmd | success | nes-callisto-003 | 254ms | [XtabProvider] 2026-03-27 13:04:07.236 [info] ccreq:9d5fac17.copilotmd | markdown 2026-03-27 13:04:07.494 [info] [fetchCompletions] Request 0670d372-b738-4f7e-ac7e-02dc621d308b at finished with 200 status after 217.69895200012252ms 2026-03-27 13:04:07.574 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:07.574 [info] request done: requestId: [472c7349-93c1-48b4-b4f2-4e60ea6bd71a] model deployment ID: [] 2026-03-27 13:04:07.576 [info] ccreq:f9e648a1.copilotmd | success | nes-callisto-003 | 244ms | [XtabProvider] 2026-03-27 13:04:07.579 [info] ccreq:b2758286.copilotmd | markdown 2026-03-27 13:04:07.825 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:07.832 [info] ccreq:221d8853.copilotmd | markdown 2026-03-27 13:04:08.485 [info] ccreq:ac298dd6.copilotmd | markdown 2026-03-27 13:04:09.396 [info] [fetchCompletions] Request e7bb413e-89bf-41e2-8562-e68686fda8f8 at finished with 200 status after 1502.3796089999378ms 2026-03-27 13:04:09.467 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:10.294 [info] [fetchCompletions] Request 6c9f0da3-dfb0-4674-b204-fafc7b10f5ef at finished with 200 status after 326.90996699989773ms 2026-03-27 13:04:10.448 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:10.455 [info] ccreq:e63909c7.copilotmd | markdown 2026-03-27 13:04:10.619 [info] ccreq:756938b1.copilotmd | markdown 2026-03-27 13:04:10.848 [info] [fetchCompletions] Request ca734729-38bf-41e4-807a-755b8707e434 at finished with 200 status after 331.2124110001605ms 2026-03-27 13:04:10.927 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:14.316 [info] [fetchCompletions] Request 11c60526-a8bc-4cae-ba41-962db5108b10 at finished with 200 status after 278.0847350000404ms 2026-03-27 13:04:14.462 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:14.468 [info] ccreq:a3905c2c.copilotmd | markdown 2026-03-27 13:04:14.749 [info] [fetchCompletions] Request 6286ca1a-ae63-408e-ab63-2f4bf569241c at finished with 200 status after 217.70455799996853ms 2026-03-27 13:04:14.822 [error] [AsyncCompletionManager] [6286ca1a-ae63-408e-ab63-2f4bf569241c] Request errored with [DOMException [AbortError]: This operation was aborted at new DOMException (node:internal/per_context/domexception:76:18) at AbortController.abort (node:internal/abort_controller:504:18) at /home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:2808:28516 at DFt.invoke (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:81562) at t.fire (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:82334) at sHe.cancel (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:83276) at NFt.cancel (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:83672) at _de.cancelRequest (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4916:2208) at _de.getFirstMatchingRequest (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4916:1960) at /home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4954:8774 at process.processTicksAndRejections (node:internal/process/task_queues:103:5)] 2026-03-27 13:04:15.064 [info] [fetchCompletions] Request 2049a846-fb2d-4497-b186-30cfdb768a92 at finished with 200 status after 240.67633599997498ms 2026-03-27 13:04:15.281 [info] [streamChoices] solution 0 returned. finish reason: [content_filter] 2026-03-27 13:04:15.288 [info] ccreq:e96c4cac.copilotmd | markdown 2026-03-27 13:04:15.583 [info] [fetchCompletions] Request c230a3c5-15a7-494f-949e-7f359c7daf8c at finished with 200 status after 455.30186600005254ms 2026-03-27 13:04:15.816 [info] [fetchCompletions] Request 71c96532-1102-4086-a2f0-ea7729a70a22 at finished with 200 status after 262.1650929998141ms 2026-03-27 13:04:15.912 [error] [AsyncCompletionManager] [71c96532-1102-4086-a2f0-ea7729a70a22] Request errored with [DOMException [AbortError]: This operation was aborted at new DOMException (node:internal/per_context/domexception:76:18) at AbortController.abort (node:internal/abort_controller:504:18) at /home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:2808:28516 at DFt.invoke (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:81562) at t.fire (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:82334) at sHe.cancel (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:83276) at NFt.cancel (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:413:83672) at _de.cancelRequest (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4916:2208) at _de.getFirstMatchingRequest (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4916:1960) at _de.getFirstMatchingRequestWithTimeout (/home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4916:920) at /home/ubuntu/.vscode-server/extensions/github.copilot-chat-0.41.2/dist/extension.js:4954:6666 at process.processTicksAndRejections (node:internal/process/task_queues:103:5)] 2026-03-27 13:04:16.295 [info] [fetchCompletions] Request a06e6ec4-7a86-4e0d-aea7-03f7afd514f7 at finished with 200 status after 378.9747549998574ms 2026-03-27 13:04:16.663 [info] [fetchCompletions] Request b1e30675-a41b-4b06-a49b-dc603a440db6 at finished with 200 status after 255.12555200001225ms 2026-03-27 13:04:16.785 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:16.792 [info] ccreq:ed41cd7d.copilotmd | markdown 2026-03-27 13:04:17.184 [info] [fetchCompletions] Request bc95da5e-fafb-4177-985e-ac814a65824e at finished with 200 status after 216.87715899990872ms 2026-03-27 13:04:17.293 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:17.302 [info] ccreq:ae1074b5.copilotmd | markdown 2026-03-27 13:04:17.568 [info] [fetchCompletions] Request 24ae60ae-c2ad-4923-98f2-cbecfe9536e2 at finished with 200 status after 202.7235609998461ms 2026-03-27 13:04:17.622 [info] ccreq:ddd66fc6.copilotmd | markdown 2026-03-27 13:04:17.653 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:17.952 [info] ccreq:e954eeb8.copilotmd | markdown 2026-03-27 13:04:18.309 [info] [fetchCompletions] Request 5bbfbe00-09ab-431a-8434-8f56cf24ff9d at finished with 200 status after 204.18873800011352ms 2026-03-27 13:04:18.397 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:18.403 [info] ccreq:279872cb.copilotmd | markdown 2026-03-27 13:04:18.502 [info] ccreq:7e7dc19e.copilotmd | markdown 2026-03-27 13:04:18.660 [info] [fetchCompletions] Request f0ed167f-b76e-4dab-b10e-b193b23aa478 at finished with 200 status after 194.1845350000076ms 2026-03-27 13:04:18.727 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:19.359 [info] [code-referencing] Calling twirp/github.snippy.v1.SnippyAPI/Match 2026-03-27 13:04:19.465 [info] [code-referencing] No match found 2026-03-27 13:04:19.589 [info] [fetchCompletions] Request f09314e3-3f55-4d4e-a41c-2365bb9bb640 at finished with 200 status after 181.4880950001534ms 2026-03-27 13:04:19.679 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:04:19.683 [info] ccreq:f97f2ee6.copilotmd | markdown 2026-03-27 13:04:21.565 [info] ccreq:5dfae1ec.copilotmd | markdown 2026-03-27 13:04:21.716 [info] ccreq:8abc25c8.copilotmd | markdown 2026-03-27 13:04:21.727 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:04:21.728 [info] request done: requestId: [e08b3474-d7e7-4780-b98b-979ac930040f] model deployment ID: [] 2026-03-27 13:04:21.730 [info] ccreq:2098531f.copilotmd | success | nes-callisto-003 | 235ms | [XtabProvider] 2026-03-27 13:04:21.738 [info] ccreq:ecaae963.copilotmd | markdown 2026-03-27 13:04:21.738 [info] ccreq:dc040b44.copilotmd | markdown 2026-03-27 13:10:24.043 [info] [fetchCompletions] Request 51ce3056-50e5-4805-a33f-dcf62c9759fb at finished with 200 status after 412.23608200019225ms 2026-03-27 13:10:24.431 [info] [fetchCompletions] Request df89d50e-874e-4a3b-9eb6-5a8243f084f5 at finished with 200 status after 198.60835699969903ms 2026-03-27 13:10:24.522 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:10:24.526 [info] ccreq:419f0cef.copilotmd | markdown 2026-03-27 13:10:24.944 [info] [fetchCompletions] Request 0141e52a-10ab-4690-a91d-addbabd6f8c2 at finished with 200 status after 292.5653679999523ms 2026-03-27 13:10:25.042 [info] [streamChoices] solution 0 returned. finish reason: [stop] 2026-03-27 13:10:25.045 [info] ccreq:d523e374.copilotmd | markdown 2026-03-27 13:10:28.465 [info] message 0 returned. finish reason: [stop] 2026-03-27 13:10:28.465 [info] request done: requestId: [6544f3f8-3885-4c44-a229-14bf0de08dd1] model deployment ID: [] 2026-03-27 13:10:28.952 [info] ccreq:d146af86.copilotmd | success | nes-callisto-003 | 764ms | [XtabProvider] 2026-03-27 13:10:28.956 [info] ccreq:f94577de.copilotmd | markdown 2026-03-27 13:10:42.427 [info] [fetchCompletions] Request 26eef249-8189-4a0e-aa10-65a6d12df2a0 at finished with 200 status after 284.87552100000903ms 2026-03-27 13:10:42.809 [info] [fetchCompletions] Request aa3942c0-0890-4ebb-a2d9-2bd5e7f89d18 at finished with 200 status after 296.1474969997071ms 2026-03-27 13:10:42.889 [info] [streamChoices] solution 0 returned. "300", "message" => "ERROR - [wsInvoiceSF] Parametros incompletos para el Servicio Web, faltan el Propoietario de la Aplicación"); $Resultado = json_encode($j_array); echo $Resultado; return; } if($bRecId == "") { $j_array = array('code' => "300", "message" => "ERROR - [wsInvoiceSF] Parametros incompletos para el Servicio Web, faltan el ID del CFDI a actualizar"); $Resultado = json_encode($j_array); echo $Resultado; return; } if($bDatPAC == "") { $j_array = array('code' => "300", "message" => "ERROR - [wsInvoiceSF] Parametros incompletos para el Servicio Web, faltan los Datos del PAC"); $Resultado = json_encode($j_array); echo $Resultado; return; } if($bDatGen == "") { $j_array = array('code' => "300", "message" => "ERROR - [wsInvoiceSF] Parametros incompletos para el Servicio Web, faltan los Datos Generales"); $Resultado = json_encode($j_array); echo $Resultado; return; } if($bDatEmi == "") { $j_array = array('code' => "300", "message" => "ERROR - [wsInvoiceSF] Parametros incompletos para el Servicio Web, faltan los Datos del Emisor"); $Resultado = json_encode($j_array); echo $Resultado; return; } if($bDatRec == "") { $j_array = array('code' => "300", "message" => "ERROR - [wsInvoiceSF] Parametros incompletos para el Servicio Web, faltan los Datos del Receptor"); $Resultado = json_encode($j_array); echo $Resultado; return; } ### 0. EXTRACCION DE PARAMETROS PARA EL CFDI ###################################################### #== Primero, extraemos el JSON del string en base 64 $bdDatPAC = base64_decode($bDatPAC); $bdDatGen = base64_decode($bDatGen); $bdDatEmi = base64_decode($bDatEmi); $bdDatRec = base64_decode($bDatRec); if($bDatRel <> "") { $bdDatRel = base64_decode($bDatRel); } #== Segundo, decodificamos el JSON a un arreglo $abdDatPAC = json_decode($bdDatPAC,true); $abdDatGen = json_decode($bdDatGen,true); $abdDatEmi = json_decode($bdDatEmi,true); $abdDatRec = json_decode($bdDatRec,true); if($bDatRel <> "") { $abdDatRel = json_decode($bdDatRel,true); } $dirBase = realpath("../"); ### CÓDIGO FUENTE, FACTURACIÓN ELECTRÓNICA CFDI VERSIÓN 3.2 ACORDE A LOS REQUIRIMIENTOS DEL SAT, ANEXO 20. ### 1. CONFIGURACIÓN INICIAL ###################################################### # 1.1 Configuración de zona horaria date_default_timezone_set('America/Mexico_City'); // # 1.2 Muestra la zona horaria predeterminada del servidor (opcional a mostrar) ### 2. DEFINICIÓN DE CONSTANTES ################################################### $SendaPEMS = "archs_pem/"; $SendaCFDI = "archs_cfdi/"; $SendaGRAFS = "archs_graf/"; $SendaXSD = "archs_xsd/"; // 2.1 Datos de acceso del usuario (proporcionados por el PAC). if($abdDatPAC["tipoTim"] == 1){ ## Timbrado en producción $urlPAC = "https://solucionfactible.com/ws/services/Timbrado"; } else { ## Timbrado en pruebas $urlPAC = "https://testing.solucionfactible.com/ws/services/Timbrado?wsdl"; } $username = $abdDatPAC["username"]; $password = $abdDatPAC["password"]; $valorNod = ''; $metodoDePago = ""; ### 3. DEFINICIÓN DE VARIABLES INICIALES ########################################## $noCertificado = $abdDatGen["Certificado_SAT"]; $file_cer = $abdDatGen["Archivo_CER"]; $file_key = $abdDatGen["Archivo_KEY"]; $organi_id_ZB = $abdDatGen["organization_id_ZB"]; $authtoken_ZB = $abdDatGen["authtoken_ZB"]; $authtoken_ZC = $abdDatGen["authtoken_ZC"]; #== Datos y Variables para OAuth Token $coa_ClientId = $abdDatGen["C_OAuth_client_id"]; $coa_ClientSecret = $abdDatGen["C_OAuth_client_secret"]; $coa_RefreshToken = $abdDatGen["C_OAuth_refresh_token"]; $coa_GrantType = $abdDatGen["C_OAuth_grant_type"]; $coa_RedirectUri = $abdDatGen["C_OAuth_redirect_uri"]; $coa_AuthUrl = "https://accounts.zoho.com/oauth/v2/token"; #---------------------------------------------------------------- # JFA: 2021-06-12 # Obtenemos el access_token #---------------------------------------------------------------- #---------------------------------------------------------------- #== Llamado para generar el token para donatello #---------------------------------------------------------------- $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => 'https://donatello.aptuslegal.app/oauth/token/f/'.$appOwner, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 0, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_HTTPHEADER => array( 'Authorization: Token e68d5a079f937ea29ad2ec5a5b105b75491a0e0c' ), )); $response = curl_exec($curl); curl_close($curl); // Decodificamos la respuesta JSON $data = json_decode($response, true); // Accedemos al valor de 'access_token de donatello' $access_token = $data['access_token']; #== Conexión a Zoho Creator para el detalle del CFDI // $request_url = 'https://creator.zoho.com/api/v2/'.$appOwner.'/'.$applnkname.'/report/cfdi_I_query/'.$bRecId; // $ch = curl_init(); // curl_setopt($ch, CURLOPT_URL, $request_url); // curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Zoho-oauthtoken ' . $access_token)); // $r = curl_exec($ch); // print_r($r); // $array = json_decode($r); // $bdDatArt = $array->data->jsonInvoiceDetail; // $abdDatArt = json_decode($bdDatArt,true); // curl_close ($ch); # === Llamada a Creator con reintentos $recordData = getZohoCreatorRecord($appOwner, $applnkname, 'cfdi_I_query', $bRecId, $access_token); # === ¿Error? if (is_string($recordData)) { echo $recordData; // Ya es un JSON con code/message return; } # === Obtener jsonInvoiceDetail del registro $bdDatArt = $recordData->jsonInvoiceDetail ?? ""; if ($bdDatArt == "") { echo json_encode([ 'code' => '300', 'message' => 'ERROR - [wsInvoiceSF] No se obtuvo el campo jsonInvoiceDetail del registro' ]); return; } # === Decodificar JSON a arreglo $abdDatArt = json_decode($bdDatArt, true); ### 4. DESTINATARIOS DE E-MAILS, PERSONAS A QUIENES LES LLEGARÁ DE MANERA ADJUNTA LA FACTURA ELECTRÓNICA EN ARCHIVOS .XML Y .PDF $CadenaEmails = $abdDatGen["CadenaEmails"]; $CadenaEmails = "ffaccinetto@aptus-legal.com"; ### 5. DATOS GENERALES DE LA FACTURA ############################################## $tipoCambioPDF = $abdDatGen["TipoCambioPDF"] ?? 1.000000; $fact_serie = $abdDatGen["fact_serie"]; $fact_folio = $abdDatGen["fact_folio"]; $NoFac = $fact_serie.$fact_folio; $fact_tipcompr = $abdDatGen["fact_tipcompr"]; $fact_exportacion = $abdDatGen["fact_exportacion"]; $tasa_iva = $abdDatGen["tasa_iva"]; $subTotal = number_format($abdDatGen["subTotal"],2,'.',''); $descuento = number_format($abdDatGen["descuento"],2,'.',''); $IVA = number_format($abdDatGen["IVA"],2,'.',''); $total = number_format($abdDatGen["total"],2,'.',''); $fecha_fact = $abdDatGen["fecha_fact"]; $condicionesDePago = $abdDatGen["condicionesDePago"]; $formaDePago = utf8_decode($abdDatGen["formaDePago"]); $metodoDePago = utf8_decode($abdDatGen["metodoDePago"]); $TipoCambio = $abdDatGen["TipoCambio"]; $LugarExpedicion = utf8_decode($abdDatGen["LugarExpedicion"]); $moneda = $abdDatGen["moneda"]; $invoiceID = $abdDatGen["invoiceID"]; $invoiceNumber = $abdDatGen["invoiceNumber"]; $TipoRelacion = $abdDatGen["TipoRelacion"]; if($TipPDFGen == 1) { $sObservaciones = base64_encode(utf8_decode($abdDatGen["Observaciones"])); $sOrdenCobro = $abdDatGen["OrdenCobro"]; } $subTotal = 0; $totalImpuestosRetenidos = 0; // 5.24 Total de impuestos retenidos. $totalImpuestosTrasladados = 0; // 5.25 Total de impuestos trasladados. // Calculando subTotal, Impuestos Trasladados y Retenidos. for ($i = 0; $icreateElement("cfdi:Comprobante"); $root = $xml->appendChild($root); $cadena_original='||'; $noatt= array(); #== 10.2 Se crea e inserta el primer nodo donde se declaran los namespaces ====== cargaAtt($root, array("xsi:schemaLocation"=>"http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd", "xmlns:cfdi"=>"http://www.sat.gob.mx/cfd/4", "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance" ) ); #== 10.3 Rutina de integración de nodos ========================================= cargaAtt($root, array( "Version"=>"4.0", "Serie"=>$fact_serie, "Folio"=>$fact_folio, "Fecha"=>date("Y-m-d")."T".date("H:i:s"), "FormaPago"=>$formaDePago, "NoCertificado"=>$noCertificado, "CondicionesDePago"=>$condicionesDePago, "SubTotal"=>$subTotal, "Descuento"=>$descuento, "Moneda"=>$moneda, "TipoCambio"=>$TipoCambio, "Total"=>$total, "TipoDeComprobante"=>$fact_tipcompr, "Exportacion"=>$fact_exportacion, "MetodoPago"=>$metodoDePago, "LugarExpedicion"=>$LugarExpedicion ) ); $emisor = $xml->createElement("cfdi:Emisor"); $emisor = $root->appendChild($emisor); cargaAtt($emisor, array("Rfc"=>$emisor_rfc, "Nombre"=>$emisor_rs, "RegimenFiscal"=>$emisor_regfis ) ); $receptor = $xml->createElement("cfdi:Receptor"); $receptor = $root->appendChild($receptor); cargaAtt($receptor, array("Rfc"=>$receptor_rfc, "Nombre"=>$receptor_rs, "DomicilioFiscalReceptor"=>$receptor_cp, "RegimenFiscalReceptor"=>$receptor_regfis, "UsoCFDI"=>$receptor_uso, "Direccion"=>$sDireRecep2 ) ); // Adicionado por FFR en Marzo 30, 2019 // Para el manejo de los CFDI Relacionados if($bDatRel <> "") { $cfdiRelacionados = $xml->createElement("cfdi:CfdiRelacionados"); $cfdiRelacionados = $root->appendChild($cfdiRelacionados); cargaAtt($cfdiRelacionados, array("TipoRelacion"=>utf8_decode($TipoRelacion))); for ($i=0; $i < count($abdDatRel); $i++) { $cfdiRelacionado = $xml->createElement("cfdi:cfdiRelacionado"); $cfdiRelacionado = $cfdiRelacionados->appendChild($cfdiRelacionado); cargaAtt($cfdiRelacionado, array("UUID"=>utf8_decode($abdDatRel[$i]["UUID"]))); } } $conceptos = $xml->createElement("cfdi:Conceptos"); $conceptos = $root->appendChild($conceptos); $isrImpuesto = "001"; $ivaImpuesto = "002"; $lIvaTraslad = false; $lIsrRetenid = false; #== 10.4 Ciclo "for", recopilación de datos de artículos e integración de sus respectivos nodos = $ivaTasaOCuota = 0.160000; for ($i=0; $icreateElement("cfdi:Concepto"); $concepto = $conceptos->appendChild($concepto); cargaAtt($concepto, array( "ClaveProdServ"=>utf8_decode($abdDatArt[$i]["claveProd"]), "Cantidad"=>$abdDatArt[$i]["cantidad"], "ClaveUnidad"=>utf8_decode($abdDatArt[$i]["unidad"]), "Unidad"=>utf8_decode($abdDatArt[$i]["descUnidad"]), "Descripcion"=>utf8_decode($abdDatArt[$i]["descripcion"]), "ValorUnitario"=>number_format($abdDatArt[$i]["valorUnitario"],2,'.',''), "Importe"=>number_format($abdDatArt[$i]["importe"],2,'.',''), "Descuento"=>number_format($abdDatArt[$i]["descuento"],2,'.',''), "ObjetoImp"=>utf8_decode($abdDatArt[$i]["ObjetoImp"]) ) ); $impuestos = $xml->createElement("cfdi:Impuestos"); $impuestos = $concepto->appendChild($impuestos); //if ($abdDatArt[$i]["ivaImporte"] > 0) if($abdDatArt[$i]["ivaImpuesto"] == "002") { $lIvaTraslad = true; $Traslados = $xml->createElement("cfdi:Traslados"); $Traslados = $impuestos->appendChild($Traslados); $Traslado = $xml->createElement("cfdi:Traslado"); $Traslado = $Traslados->appendChild($Traslado); $ivaImpuesto = $abdDatArt[$i]["ivaImpuesto"]; cargaAtt($Traslado, array( "Base"=>number_format($abdDatArt[$i]["ivaBase"],2,'.',''), "Impuesto"=>$abdDatArt[$i]["ivaImpuesto"], "TipoFactor"=>$abdDatArt[$i]["ivaTipoFactor"], "TasaOCuota"=>number_format($abdDatArt[$i]["ivaTasaOCuota"],6,'.',''), "Importe"=>number_format($abdDatArt[$i]["ivaImporte"],2,'.','') ) ); $ivaTasaOCuota = $abdDatArt[$i]["ivaTasaOCuota"]; } //if ($abdDatArt[$i]["isrImporte"] > 0) if($abdDatArt[$i]["isrImpuesto"] == "001") { $lIsrRetenid = true; $Retenciones = $xml->createElement("cfdi:Retenciones"); $Retenciones = $impuestos->appendChild($Retenciones); $Retencion = $xml->createElement("cfdi:Retencion"); $Retencion = $Retenciones->appendChild($Retencion); $isrImpuesto = $abdDatArt[$i]["isrImpuesto"]; cargaAtt($Retencion, array( "Base"=>number_format($abdDatArt[$i]["isrBase"],2,'.',''), "Impuesto"=>$abdDatArt[$i]["isrImpuesto"], "TipoFactor"=>$abdDatArt[$i]["isrTipoFactor"], "TasaOCuota"=>number_format($abdDatArt[$i]["isrTasaOCuota"],6,'.',''), "Importe"=>number_format($abdDatArt[$i]["isrImporte"],2,'.','') ) ); } } $Impuestos = $xml->createElement("cfdi:Impuestos"); $Impuestos = $root->appendChild($Impuestos); //if ($totalImpuestosTrasladados > 0) if ($lIvaTraslad == true) { $Traslados = $xml->createElement("cfdi:Traslados"); $Traslados = $Impuestos->appendChild($Traslados); $Traslado = $xml->createElement("cfdi:Traslado"); $Traslado = $Traslados->appendChild($Traslado); cargaAtt($Traslado, array( "Impuesto"=>$ivaImpuesto, "TipoFactor"=>"Tasa", "TasaOCuota"=>number_format($ivaTasaOCuota,6,'.',''), "Importe"=>number_format($totalImpuestosTrasladados,2,'.','') ) ); cargaAtt($Impuestos, array( "TotalImpuestosTrasladados"=>number_format($totalImpuestosTrasladados,2,'.','') ) ); } //if ($totalImpuestosRetenidos > 0) if ($lIsrRetenid == true) { $Retenciones = $xml->createElement("cfdi:Retenciones"); $Retenciones = $Impuestos->appendChild($Retenciones); $Retencion = $xml->createElement("cfdi:Retencion"); $Retencion = $Retenciones->appendChild($Retencion); cargaAtt($Retencion, array( "Impuesto"=>$isrImpuesto, "Importe"=>number_format($totalImpuestosRetenidos,2,'.','') ) ); cargaAtt($Impuestos, array( "TotalImpuestosRetenidos"=>number_format($totalImpuestosRetenidos,2,'.','') ) ); } $complemento = $xml->createElement("cfdi:Complemento"); $complemento = $root->appendChild($complemento); #== 10.7 Termina de conformarse la "Cadena original" con doble || $cadena_original .= "|"; #=== Muestra la cadena original (opcional a mostrar) ======================= #== 10.8 Proceso para obtener el sello digital del archivo .pem.key ========= $keyid = openssl_get_privatekey(file_get_contents($SendaPEMS.$file_key)); openssl_sign($cadena_original, $crypttext, $keyid, OPENSSL_ALGO_SHA256); openssl_free_key($keyid); #== 10.9 Se convierte la cadena digital a Base 64 =========================== $sello = base64_encode($crypttext); #=== Muestra el sello (opcional a mostrar) ================================= #== 10.10 Proceso para extraer el certificado del sello digital ================== $file = $SendaPEMS.$file_cer; // Ruta al archivo $datos = file($file); $certificado = ""; $carga=false; for ($i=0; $isetAttribute("sello",$sello); $root->setAttribute("certificado",$certificado); # Certificado. //$root->setAttribute("motivoDescuento",$MotivDesc); # Motivo de descuento. //$root->setAttribute("folio",$fact_folio); # Folio de la factura (número entero). //$root->setAttribute("serie",$fact_serie); # Serie de la factura (letra o letras). #== Fin de la integración de nodos ========================================= #=== 10.12 Se guarda el archivo .XML antes de ser timbrado ======================= $NomArchCFDI = $SendaCFDI."PreCFDI-33_".$NoFac.".xml"; $NomArchPDF1 = $SendaCFDI."VP_CFDI_".$fact_serie.str_pad($fact_folio, 6, "0", STR_PAD_LEFT)."_".$invoiceNumber.".pdf"; $NomArchXML = "PreCFDI-33_".$NoFac.".xml"; $NomArchPDF = "VP_CFDI_".$fact_serie.str_pad($fact_folio, 6, "0", STR_PAD_LEFT)."_".$invoiceNumber.".pdf"; // $cfdi = $xml->saveXML(); $xml->formatOutput = true; $xml->save($NomArchCFDI); // Guarda el archivo .XML (sin timbrar) en el directorio predeterminado. unset($xml); #=== 10.13 Se dan permisos de escritura al archivo .xml. ========================= chmod($NomArchCFDI, 0777); ### 13. CREACION DE PDF Y ENVIO DE CORREO CON XML Y PDF ########################### # 13.1 Creación del Archivo PDF //$url = 'http://localhost/aptusCFDIRF/wsInvoicePDF_VP_v33.php?NomArchXML='.$NomArchXML.'&NomArchPDF='.$NomArchPDF; print_r($TipPDFGen); if($TipPDFGen == 1) { $url = 'https://aptuslegal.app/aptusCFDIRF/'.$FormatoPDF.'.php?NomArchXML='.$NomArchXML.'&NomArchPDF='.$NomArchPDF.'&pInvNumb='.$invoiceNumber.'&pOrdCob='.$sOrdenCobro.'&pObserva='.$sObservaciones.'&pDirRecep='.$sDireRecep.'&tipoCambioPDF='.$tipoCambioPDF; } else { $url = 'https://aptuslegal.app/aptusCFDIRF/'.$FormatoPDF.'.php?NomArchXML='.$NomArchXML.'&NomArchPDF='.$NomArchPDF.'&tipoCambioPDF='.$tipoCambioPDF; } // echo $url; // echo '
'; $objCurl = curl_init(); curl_setopt($objCurl, CURLOPT_URL, $url); curl_setopt($objCurl, CURLOPT_HEADER, 0); curl_setopt($objCurl, CURLOPT_RETURNTRANSFER, true); curl_exec($objCurl); //curl_close($objCurl); # Usamos la funcion curl_errno() para atrapar cualquier error relacionado con la llamada. if (curl_errno($objCurl)) { $error_msg = curl_error($objCurl); } curl_close ($objCurl); if (isset($error_msg)) { $j_array = array('code' => '666', 'message' => "Error en la llamada curl " . $error_msg); $Resultado = json_encode($j_array); echo $Resultado; return; } # 13.3 Envio del Archivos a Invoice de Zoho Creator # Primero el PDF $file_name_with_full_path = '/var/www/html/aptusCFDIRF/archs_cfdi/'.$NomArchPDF; //$request_url = 'https://creator.zoho.com/api/xml/fileupload/scope=creatorapi'; # Actualización a API V2 y OAuth JFA y FFR 2021-06-18 $request_url = 'https://creator.zoho.com/api/v2/'.$appOwner.'/'.$applnkname.'/report/cfdi_I_query/'.$bRecId.'/File_PDF_VP/upload'; if (function_exists('curl_file_create')) { // php 5.6+ $cFile = curl_file_create($file_name_with_full_path); } else { $cFile = '@' . realpath($file_name_with_full_path); } /* echo $bRecId; echo "
"; echo $authtoken_ZC; echo "
"; echo $NomArchPDF; echo "
"; echo $file_name_with_full_path; echo "
"; */ // 'applinkname' => 'cfdi', $post = array( //'authtoken' => $authtoken_ZC, //'applinkname' => $applnkname, //'formname' => 'CreacionCFDIv33', //'fieldname' => 'File_PDF_VP', //'recordId' => $bRecId, 'filename' => $NomArchPDF, 'file'=> $cFile); #---------------------------------------------------------------- #== Llamado para generar el token para donatello #---------------------------------------------------------------- $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => 'https://donatello.aptuslegal.app/oauth/token/'.$appOwner, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 0, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_HTTPHEADER => array( 'Authorization: Token e68d5a079f937ea29ad2ec5a5b105b75491a0e0c' ), )); $response = curl_exec($curl); curl_close($curl); // Decodificamos la respuesta JSON $data = json_decode($response, true); // Accedemos al valor de 'access_token de donatello' $access_token = $data['access_token']; // $ch = curl_init(); // curl_setopt($ch, CURLOPT_URL, $request_url); // //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // curl_setopt($ch, CURLOPT_POST, true); // curl_setopt($ch, CURLOPT_POSTFIELDS, $post); // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // #JFA: Cambiamos el método de Autenticación a OAuth // curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Zoho-oauthtoken ' . $access_token)); // $r = curl_exec($ch); // curl_close ($ch); try { $result = uploadPdfWithRetries($request_url, $post, $access_token); $j_array = array('code' => "200", "message" => "Proceso de creacion de CFDI fue exitoso", 'file_upload' => $result); $Resultado = json_encode($j_array); echo $Resultado; return; } catch (Exception $e) { error_log($e->getMessage()); $j_array = array('code' => "500", "message" => "Error no pudo añadirse el PDF", 'Error' => $e->getMessage()); $Resultado = json_encode($j_array); echo $Resultado; return; } #== Se elimina los archivos de trabajo //unlink($NomArchCFDI); //unlink($NomArchPDF1); $j_array = array('code' => "200", "message" => "Proceso de creacion de CFDI fue exitoso", 'file_upload' => $NomArchPDF); $Resultado = json_encode($j_array); echo $Resultado; return; ### 14. FUNCIONES DEL MÓDULO ######################################################### # 14.1 Función que integra los nodos al archivo .XML y forma la "Cadena original". function cargaAtt(&$nodo, $attr){ global $xml, $cadena_original; $quitar = array('sello'=>1,'noCertificado'=>1,'certificado'=>1); foreach ($attr as $key => $val){ $val = preg_replace('/\s\s+/', ' ', $val); $val = trim($val); if (strlen($val)>0){ $val = utf8_encode(str_replace("|","/",$val)); $nodo->setAttribute($key,$val); if (!isset($quitar[$key])) if (substr($key,0,3) != "xml" && substr($key,0,4) != "xsi:") $cadena_original .= $val . "|"; } } } # 14.2 Funciónes que da formato al "Importe total" como lo requiere el SAT para ser integrado al código QR. function ProcesImpTot($ImpTot){ $ArrayImpTot = explode(".", $ImpTot); $NumEnt = $ArrayImpTot[0]; $NumDec = ProcesDecFac($ArrayImpTot[1]); return $NumEnt.".".$NumDec; } function ProcesDecFac($Num){ $FolDec = ""; if ($Num < 10){$FolDec = "00000".$Num;} if ($Num > 9 and $Num < 100){$FolDec = $Num."0000";} if ($Num > 99 and $Num < 1000){$FolDec = $Num."000";} if ($Num > 999 and $Num < 10000){$FolDec = $Num."00";} if ($Num > 9999 and $Num < 100000){$FolDec = $Num."0";} return $FolDec; } function getZohoCreatorRecord($appOwner, $appLinkName, $reportLinkName, $recordId, $access_token, $maxRetries = 3) { $base_url = 'https://www.zohoapis.com/creator/v2.1'; if($appOwner == 'ajmartinezfp'){ $appOwner == 'ajmartinez'; } $request_url = "$base_url/data/$appOwner/$appLinkName/report/$reportLinkName/$recordId"; $headers = [ "Authorization: Zoho-oauthtoken $access_token", "Accept: application/json" ]; $retryCount = 0; $waitSeconds = 1; while ($retryCount < $maxRetries) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $request_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = json_decode($response); if ($httpCode === 200 && isset($data->data)) { return $data->data; } $errorCode = $data->code ?? 'N/A'; $errorMsg = $data->message ?? 'Respuesta inválida'; if ($httpCode === 401 || $errorCode === 1030) { return json_encode([ 'code' => '401', 'message' => 'ERROR - Token de acceso expirado o inválido para Zoho Creator' ]); } if ($errorCode === 3190) { return json_encode([ 'code' => '404', 'message' => "ERROR - No se encontró el registro con ID $recordId en el reporte $reportLinkName" ]); } if ($httpCode === 429 || $errorCode === 2955) { sleep($waitSeconds); $waitSeconds *= 2; $retryCount++; continue; } if (!isset($data->data)) { sleep($waitSeconds); $retryCount++; continue; } return json_encode([ 'code' => '500', 'message' => "ERROR - Fallo inesperado en Zoho Creator. HTTP $httpCode - Código $errorCode - $errorMsg" ]); } return json_encode([ 'code' => '408', 'message' => "ERROR - Tiempo de espera agotado tras $maxRetries intentos para obtener el registro $recordId en ".$request_url ]); } /** * Sube un PDF a Zoho Creator con reintentos y back-off exponencial. * * @param string $request_url URL de la API de Creator para el upload. * @param array $post Array con 'filename' y 'file'. * @param string $access_token Token OAuth válido. * @param int $maxRetries Número máximo de intentos (por defecto 5). * @param int $baseDelay Retardo base en segundos (por defecto 1). * * @return string Respuesta cruda del servidor en caso de éxito. * @throws Exception Si se agotan los reintentos o hay errores de cURL/HTTP. */ function uploadPdfWithRetries($request_url, array $post, $access_token, $maxRetries = 5, $baseDelay = 1) { $errors = []; for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $request_url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $post, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => [ 'Authorization: Zoho-oauthtoken ' . $access_token ], // CURLOPT_SSL_VERIFYPEER => false, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $errNo = curl_errno($ch); $errMsg = curl_error($ch); curl_close($ch); // Intento: siempre parseamos la respuesta para ver si hay error de API $apiError = false; $body = $response; // 1) Si hubo fallo de cURL o HTTP fuera de 2xx, es error if ($response === false || $httpCode < 200 || $httpCode >= 300) { $apiError = true; $errors[] = sprintf( 'Intento %d/%d: HTTP %d, cURL[%d]: %s', $attempt, $maxRetries, $httpCode, $errNo, $errMsg ); } else { // 2) HTTP OK, pero comprobamos Zoho-level $json = json_decode($body, true); if (json_last_error() === JSON_ERROR_NONE && isset($json['code']) && $json['code'] != 3000) { $apiError = true; $errors[] = sprintf( 'Intento %d/%d: Zoho API error code %d: %s', $attempt, $maxRetries, $json['code'], $json['message'] ?? '(sin mensaje)' ); } } // 3) Si no hubo **ningún** error, devolvemos (respuesta + todos los errores previos) if (!$apiError) { return [ 'response' => $body, 'errors' => $errors ]; } // 4) Si hubo error, esperamos back-off y reintentamos $delay = $baseDelay * (2 ** ($attempt - 1)); sleep($delay); } // Si llegamos aquí, todos los intentos fallaron $errorText = implode("\n", $errors); throw new Exception( "No se pudo subir el PDF tras {$maxRetries} intentos. Errores:\n{$errorText}" ); }