2025-07-17 17:19:00) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:02) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:04) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:06) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:08) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:10) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:12) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:14) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:16) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:18) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:20) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:22) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:24) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:26) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:28) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:30) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:32) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:34) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:36) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:38) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:40) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:42) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:44) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:46) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:48) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:50) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:52) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:54) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:56) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:19:58) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:00) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:02) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:04) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:06) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:08) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:10) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:12) - Error al guardar imagen localmente [66afeb15-abaf-429a-a873-6cad96fd4f35](2025-07-17 17:20:13) - Proceso de subida de imagen iniciado, data: {"source":"webhook","RefAL":"MAR-130904","MarcaId":"4597192000273229615","user":"bmeza@arochilindner.com"} [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:14) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:16) - Error al guardar imagen localmente [52569634-b01c-4a94-8732-4a86bc217515](2025-07-17 17:20:18) - Error al guardar imagen localmente [66afeb15-abaf-429a-a873-6cad96fd4f35](202 "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; } /* if($bDatArt == "") { $j_array = array('code' => "300", "message" => "ERROR - [wsInvoiceSF] Parametros incompletos para el Servicio Web, faltan los Datos de los Articulos"); $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); ##$bdDatArt = base64_decode($bDatArt); if($bDatRel <> "") { $bdDatRel = base64_decode($bDatRel); } if ($tipoTest == 1){ echo $bdDatPAC; echo '
'; echo '
'; echo $bdDatGen; echo '
'; echo '
'; echo $bdDatEmi; echo '
'; echo '
'; echo $bdDatRec; echo '
'; echo '
'; echo $bdDatArt; echo '
'; echo '
'; echo $bdDatRel; echo '
'; echo '
'; } #== 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); ##$abdDatArt = json_decode($bdDatArt,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'); // if($tipoTest > 0){ # 1.2 Muestra la zona horaria predeterminada del servidor (opcional a mostrar) echo '
'; echo 'ZONA HORARIA PREDETERMINADA'; echo '
'; echo '
'; echo date_default_timezone_get(); echo '

'; } ### 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?wsdl"; } else { ## Timbrado en pruebas $urlPAC = "https://testing.solucionfactible.com/ws/services/Timbrado?wsdl"; } $username = $abdDatPAC["username"]; $password = $abdDatPAC["password"]; if($tipoTest > 0){ $username = "testing@solucionfactible.com"; $password = "timbrado.SF.16672"; $urlPAC = "https://testing.solucionfactible.com/ws/services/Timbrado?wsdl"; } $valorNod = ''; $metodoDePago = ""; if ($tipoTest > 0){ ### MUESTRA LOS DATOS DEL USUARIO QUE ESTÁ TIMBRANDO (OPCIONAL A MOSTRAR) ###### echo '
'; echo 'DATOS DEL USUARIO QUE ESTÁ TIMBRANDO'; echo '
'; echo '
'; echo 'USUARIO: '.$username."
"; echo 'PASSWORD: '.$password."
"; echo 'URL: '.$urlPAC."
"; echo '

'; } ### 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"; $access_token = getDonatelloToken($appOwner, false); if (!$access_token) { // 2) Si no había token válido (o expiró), forzamos uno nuevo $access_token = getDonatelloToken($appOwner, true); if (!$access_token) { echo json_encode(['code'=>'401','message'=>'No se pudo obtener token de Donatello']); return; } } #== Conexión a Zoho Creator para el detalle del CFDI # 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; // **Primer intento** con el token existente $result = callZohoCreator($request_url, $access_token); if (!$result['success']) { // **Falla**: regeneramos token (/f/) y reintentamos una vez $access_token = getDonatelloToken($appOwner, true); if (!$access_token) { echo json_encode(['code'=>'401','message'=>'No se pudo regenerar token de Donatello']); return; } $result = callZohoCreator($request_url, $access_token); if (!$result['success']) { echo json_encode(['code'=>'502','message'=>'Error al obtener datos de Zoho Creator']); return; } } // **Éxito**: usamos el JSON decodificado $array = (object)[ 'data' => (object)$result['decoded']['data'] ]; if($incAddenda == 1) { $folio_AdAs = $array->data->Folio_AdAs; $serie_AdAs = $array->data->Serie_AdAs; $no_Prov_AdAs = $array->data->No_Prov_AdAs; $ord_Com_AdAs = $array->data->Ord_Comp_AdAs; $tip_Pro_AdAs = $array->data->Tipo_Prov_AdAs; $jsonAddenda = $array->data->JSON_Addenda_ASO; $ajsonAddenda = json_decode($jsonAddenda,true); } $bdDatArt = $array->data->jsonInvoiceDetail; $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 ############################################## $fact_serie = $abdDatGen["fact_serie"]; $fact_folio = $abdDatGen["fact_folio"]; if ($bFolioN <> ""){ $fact_folio = $bFolioN; } $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; $subTotTraslados = 0; $totalImpuestosRetenidos = 0; // 5.24 Total de impuestos retenidos. $totalImpuestosTrasladados = 0; // 5.25 Total de impuestos trasladados. if ($tipoTest > 0){ ### 6. MUESTRA LA ZONA HORARIA PREDETERMINADA DEL SERVIDOR (OPCIONAL A MOSTRAR) ###### echo '
'; echo 'FECHA Y HORA DE SOLICITUD DE TIMBRADO'; echo '
'; echo '
'; echo $fecha_fact; // 6.1 Se muestra solo para consultar y confirmar que sea la correcta. echo '

'; } // Calculando subTotal, Impuestos Trasladados y Retenidos. for ($i = 0; $i "702", "message" => "ERROR - [wsInvoiceSF] El CFDI ya ha sido timbrado previamente. No se puede timbrar de nuevo.", "fileXML" => $NomArchXML, "filePDF" => $NomArchPDF); // $Resultado = json_encode($j_array); // echo $Resultado; // return; // } ### 10. CREACIÓN Y ALMACENAMIENTO DEL ARCHIVO .XML (CFDI) ANTES DE SER TIMBRADO ################### #== 10.1 Creación de la variable de tipo DOM, aquí se conforma el XML a timbrar posteriormente. $xml = new DOMdocument('1.0', 'UTF-8'); $root = $xml->createElement("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 ========================================= if($TipPDFGen == 1) { if ($appOwner == "arochiylindner.aptus") { cargaAtt($root, array( "Version"=>"4.0", "Serie"=>$fact_serie, "Folio"=>$invoiceNumber, "Fecha"=>$fecha_fact, "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 ) ); } else { cargaAtt($root, array( "Version"=>"4.0", "Serie"=>$fact_serie, "Folio"=>$invoiceNumber, "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 ) ); } } else { 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 ) ); } // 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"]))); } } $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 ) ); $conceptos = $xml->createElement("cfdi:Conceptos"); $conceptos = $root->appendChild($conceptos); $isrImpuesto = "001"; $ivaImpuesto = "002"; #== 10.4 Ciclo "for", recopilación de datos de artículos e integración de sus respectivos nodos == $ivaTasaOCuota = 0.160000; $isrTasaOCuota = 0.060000; $hasTraslado002 = false; // se activará si aparece IVA 002 en cualquier concepto $ivaTasaOCuota = '0.000000'; // la última tasa vista (sirve para el global) 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"=>trim(utf8_decode($abdDatArt[$i]["descUnidad"])), "Descripcion"=>trim(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"]) ) ); $generaImpuestos = ( $abdDatArt[$i]['ObjetoImp'] == '02' || $abdDatArt[$i]['ivaImporte'] > 0 || $abdDatArt[$i]['isrImporte'] > 0 ); if ($generaImpuestos) { $impuestos = $xml->createElement("cfdi:Impuestos"); $impuestos = $concepto->appendChild($impuestos); if ($abdDatArt[$i]["ivaImporte"] > 0) //if($abdDatArt[$i]["ivaImpuesto"] == "002") { $Traslados = $xml->createElement("cfdi:Traslados"); $Traslados = $impuestos->appendChild($Traslados); $Traslado = $xml->createElement("cfdi:Traslado"); $Traslado = $Traslados->appendChild($Traslado); $importeIva = number_format($abdDatArt[$i]['ivaImporte'], 2, '.', ''); $tasaIva = ($abdDatArt[$i]['ivaImporte'] > 0) ? number_format($abdDatArt[$i]['ivaTasaOCuota'], 6, '.', '') : '0.000000'; $hasTraslado002 = true; $ivaTasaOCuota = $tasaIva; // guarda 0.000000 o la tasa real cargaAtt($Traslado, array( 'Base' => number_format($abdDatArt[$i]['importe'] - $abdDatArt[$i]['descuento'], 2, '.', ''), //ESTE ES UNO DE LOS ERRRRR 'Impuesto' => '002', 'TipoFactor' => 'Tasa', 'TasaOCuota' => number_format($tasaIva, 6, '.', ''), 'Importe' => $importeIva )); /* Solo suma al global si el importe es > 0 */ if ($abdDatArt[$i]['ivaImporte'] > 0) { // $totalImpuestosTrasladados += $abdDatArt[$i]['ivaImporte']; $ivaTasaOCuota = $abdDatArt[$i]['ivaTasaOCuota']; } } if ($abdDatArt[$i]["isrImporte"] > 0) //if($abdDatArt[$i]["isrImpuesto"] == "001") { $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,'.','') ) ); $isrTasaOCuota = $abdDatArt[$i]["isrTasaOCuota"]; } } } if ($hasTraslado002 || $totalImpuestosRetenidos > 0) { $Impuestos = $xml->createElement("cfdi:Impuestos"); $Impuestos = $root->appendChild($Impuestos); if ($totalImpuestosRetenidos > 0) { $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,'.','') ) ); } if ($hasTraslado002) { $Traslados = $xml->createElement("cfdi:Traslados"); $Traslados = $Impuestos->appendChild($Traslados); $Traslado = $xml->createElement("cfdi:Traslado"); $Traslado = $Traslados->appendChild($Traslado); cargaAtt($Traslado, array( "Base"=>number_format($subTotTraslados,2,'.',''), "Impuesto"=>$ivaImpuesto, "TipoFactor"=>"Tasa", "TasaOCuota"=>number_format($ivaTasaOCuota,6,'.',''), "Importe"=>number_format($totalImpuestosTrasladados,2,'.','') ) ); cargaAtt($Impuestos, array( "TotalImpuestosTrasladados"=>number_format($totalImpuestosTrasladados,2,'.','') ) ); } } $complemento = $xml->createElement("cfdi:Complemento"); $complemento = $root->appendChild($complemento); #== 10.7 Termina de conformarse la "Cadena original" con doble || $cadena_original .= "|"; if ($tipoTest > 0){ #=== Muestra la cadena original (opcional a mostrar) ======================= echo '
'; echo 'CADENA ORIGINAL'; echo '
'; echo '
'; echo $cadena_original; echo '

'; } #== 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); if ($tipoTest > 0){ #=== Muestra el sello (opcional a mostrar) ================================= echo '
'; echo 'SELLO'; echo '
'; echo '
'; echo $sello; echo '

'; } #== 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; $i 0){ #=== Muestra el certificado del sello digital (opcional a mostrar) ========= echo '
'; echo 'CERTIFICADO DEL SELLO DIGITAL'; echo '
'; echo '
'; echo $certificado; echo '

'; } #== 10.11 Se continua con la integración de nodos =========================== $root->setAttribute("Sello",$sello); $root->setAttribute("Certificado",$certificado); # Certificado. #== Fin de la integración de nodos ========================================= #=== 10.12 Se guarda el archivo .XML antes de ser timbrado ======================= $cfdi = $xml->saveXML(); $xml->formatOutput = true; $xml->save($SendaCFDI."PreCFDI-33_".$NoFac.".xml"); unset($xml); #=== 10.13 Se dan permisos de escritura al archivo .xml. ========================= chmod($SendaCFDI."PreCFDI-33_".$NoFac.".xml", 0777); ### 11. PROCESO DE TIMBRADO ######################################################## if ($tipoTest > 0){ #=== Se muestra el .XML antes de ser timbrado (opcional a mostrar)========== echo '
'; echo 'FACTURA .XML A TIMBRAR'; echo '
'; echo '
'; echo htmlspecialchars($cfdi); echo '

'; } #== 11.1 Se crea una variable de tipo DOM y se le carga el CFDI ================================= $xml2 = new DOMDocument(); $xml2->loadXML($cfdi); #== 11.2 Proceso de validación de la variable de tipo DOM (estructura del CFDI a timbrar) contra el esquema cfdv32.xsd ========================== $fname = $SendaCFDI."PreCFDI-33_".$NoFac.".xml"; if(!file_exists($fname)){ die(PHP_EOL . "File not found" . PHP_EOL . PHP_EOL); } $handle = fopen($fname, "r"); $sData = ''; #== 11.3 Convirtiendo el contenido del CFDI a BASE 64 ====================== while(!feof($handle)) $sData .= fread($handle, filesize($fname)); fclose($handle); $b64 = base64_encode($sData); $response = ''; if($abdDatPAC["tipoTim"] == 1){ ## Timbrado en producción $urlLocation = 'https://solucionfactible.com/ws/services/Timbrado'; } else { ## Timbrado en pruebas $urlLocation = 'https://testing.solucionfactible.com/ws/services/Timbrado'; } #== 11.5 Se lleva a cabo el timbrado del XML ============================ try { $client = new SoapClient($urlPAC); $client->__setLocation($urlLocation); $params = array('usuario' => $username, 'password' => $password, 'cfdiBase64'=>$b64, 'zip'=>False); $response = $client->__soapCall('timbrarBase64', array('parameters' => $params)); } catch (SoapFault $fault) { $j_array = array('code' => "400", "message" => $fault->faultcode."-".$fault->faultstring); $Resultado = json_encode($j_array); echo $Resultado; return; } $ret = $response->return; if($ret->status != 200) { $j_array = array('code' => "500", "message" => "ERROR EN EL PROCESO DE TIMBRADO. ".$ret->status." - ".$ret->mensaje); $Resultado = json_encode($j_array); echo $Resultado; return; } /* if ($tipoTest > 0){ echo '
'; print_r($ret); echo '
'; return; } */ #=== 11.7 Muestra los resultados del timbrado del CFDI ========================= $RespServ = $ret->resultados->cfdiTimbrado; if($RespServ == NULL){ $j_array = array('code' => "600", "message" => "ERROR EN EL PROCESO DE TIMBRADO. status: ".$ret->status.", mensaje: ".$ret->mensaje.". ==> Resultado Timbrado: "." status: ".$ret->resultados->status.", mensaje: ".$ret->resultados->mensaje); $Resultado = json_encode($j_array); echo $Resultado; return; /* //echo $cadena_original; echo "status: ".$ret->resultados->status.", mensaje: ".$ret->resultados->mensaje; echo "
"; echo "
"; print_r($ret); echo "
"; echo "
"; die(PHP_EOL . "Error al timbrar el CFDI." . PHP_EOL . PHP_EOL); */ } if ($tipoTest > 0){ #== 12.5 Se muestra el .XML ya timbrado (CFDI V 3.2), opcional a mostrar ===== echo '
'; echo 'FACTURA .XML (CFDI) YA TIMBRADA'; echo '
'; echo '
'; echo htmlspecialchars($RespServ); echo '

'; } ## 12. PROCESOS POSTERIORES AL TIMBRADO ######################################## #== 12.1 Se asigna la respuesta del servidor a una variable de tipo DOM ==== $VarXML = new DOMDocument(); $VarXML->loadXML($RespServ); #== 12.2 Se graba la respuesta del servidor a un archivo .xml $VarXML->save($SendaCFDI."RespServ_".$NoFac.".xml"); chmod($SendaCFDI."RespServ_".$NoFac.".xml", 0777); if($TipPDFGen == 1) { $NomArchXML = "CFDI_".$fact_serie."_".$invoiceNumber.".xml"; $NomArchPDF = "CFDI_".$fact_serie."_".$invoiceNumber.".pdf"; } else { $NomArchXML = "CFDI_".$fact_serie.str_pad($fact_folio, 6, "0", STR_PAD_LEFT)."_".$invoiceNumber.".xml"; $NomArchPDF = "CFDI_".$fact_serie.str_pad($fact_folio, 6, "0", STR_PAD_LEFT)."_".$invoiceNumber.".pdf"; } //$NomArchXML = "CFDI_".$fact_serie.str_pad($fact_folio, 6, "0", STR_PAD_LEFT)."_".$invoiceNumber.".xml"; //$NomArchPDF = "CFDI_".$fact_serie.str_pad($fact_folio, 6, "0", STR_PAD_LEFT)."_".$invoiceNumber.".pdf"; $xmlt = new DOMDocument(); $xmlt->loadXML($RespServ); #== 12.1.1 Se valida si tiene addenda y se le agrega al CFDI ==== if($incAddenda == 1) { if ($tipoTest > 0){ echo '
'; echo 'DATOS DE LA ADDENDA'; echo '
'; echo '
'; echo 'tipoProveedor: '.$tip_Pro_AdAs.'
'; echo 'folio: '.$folio_AdAs.'
'; echo 'ordenCompra: '.$ord_Com_AdAs.'
'; echo 'noProveedor: '.$no_Prov_AdAs.'
'; echo 'serie: '.$serie_AdAs.'
'; echo 'cuantos: '.count($ajsonAddenda).'
'; for ($i = 0; $i'.$ajsonAddenda[$i]["ivaDevengado"].'
'; echo 'ivaAcreditable('.$i.'): '.$ajsonAddenda[$i]["ivaAcreditable"].'
'; echo 'noPartida('.$i.'): '.$ajsonAddenda[$i]["noPartida"].'
'; echo 'Otros('.$i.'): '.$ajsonAddenda[$i]["Otros"].'
'; }; echo '

'; } $Root = $xmlt->firstChild; $addenda = $xmlt->createElement("cfdi:Addenda"); $addenda = $Root->appendChild($addenda); $asonioscoc = $xmlt->createElement("ASONIOSCOC"); $asonioscoc = $addenda->appendChild($asonioscoc); cargaAttNodo($asonioscoc, array( "tipoProveedor"=>$tip_Pro_AdAs, "folio"=>$folio_AdAs, "ordenCompra"=>$ord_Com_AdAs, "noProveedor"=>$no_Prov_AdAs, "serie"=>$serie_AdAs ) ); $partidas = $xmlt->createElement("Partidas"); $partidas = $asonioscoc->appendChild($partidas); for ($i = 0; $icreateElement("Partida"); $partida = $partidas->appendChild($partida); cargaAttNodo($partida, array( "ivaDevengado"=>$ajsonAddenda[$i]["ivaDevengado"], "ivaAcreditable"=>$ajsonAddenda[$i]["ivaAcreditable"], "noPartida"=>$ajsonAddenda[$i]["noPartida"], "Otros"=>$ajsonAddenda[$i]["Otros"] ) ); }; } $xmlt->save($SendaCFDI.$NomArchXML); chmod($SendaCFDI.$NomArchXML, 0777); #== 12.7 Procesos para extraer datos del Timbre Fiscal del CFDI ========= $docXML = new DOMDocument(); //$docXML->load($SendaCFDI."Fact_CFDI_".$NoFac.".xml"); $docXML->load($SendaCFDI.$NomArchXML); $comprobante = $docXML->getElementsByTagName("TimbreFiscalDigital"); #== 12.8 Se obtienen contenidos de los atributos y se asignan a variables para ser mostrados ======= foreach($comprobante as $timFis){ $version_timbre = $timFis->getAttribute('Version'); $sello_SAT = $timFis->getAttribute('SelloSAT'); $cert_SAT = $timFis->getAttribute('NoCertificadoSAT'); $sello_CFD = $timFis->getAttribute('SelloCFD'); $tim_fecha = $timFis->getAttribute('FechaTimbrado'); $tim_uuid = $timFis->getAttribute('UUID'); $tim_RfcPac = $timFis->getAttribute('RfcProvCertif'); } if ($tipoTest > 0){ echo 'Versión del timbre: '.$version_timbre.'
'; echo 'Sello del SAT: '.$sello_SAT.'
'; echo 'Certificado del SAT: '.$cert_SAT.'
'; echo 'Sello del CFDI: '.$sello_CFD.'
'; echo 'Fecha de timbrado: '.$tim_fecha.'
'; echo 'Folio fiscal: '.$tim_uuid.'
'; echo 'RFC del PAC: '.$tim_RfcPac.'
'; echo 'No. de factura asignado por el sistema local: '.$NoFac.'

'; } #== 12.8.1 Se muestra el número de factura asignado por el sistema local (no asingado por el SAT). $params = $docXML->getElementsByTagName('Emisor'); foreach ($params as $param) { $Emisor_RFC = $param->getAttribute('Rfc'); } $params = $docXML->getElementsByTagName('Receptor'); foreach ($params as $param) { $Receptor_RFC = $param->getAttribute('Rfc'); } $params = $docXML->getElementsByTagName('Comprobante'); foreach ($params as $param) { $total = $param->getAttribute('Total'); } #== 12.9 Se crea el archivo .PNG con codigo bidimensional ================================= $filename = "archs_graf/Img_".$tim_uuid.".png"; ### 13. CREACION DE PDF ########################### # 13.1 Creación del Archivo PDF //$url = 'http://64.235.39.50/aptusCFDIRF/wsInvoicePDF_v33.php?NomArchXML='.$NomArchXML.'&NomArchPDF='.$NomArchPDF; if($TipPDFGen == 1) { $url = 'https://aptuslegal.app/aptusCFDIRF/'.$FormatoPDF.'.php?NomArchXML='.$NomArchXML.'&NomArchPDF='.$NomArchPDF.$NomArchPDF.'&pInvNumb='.$invoiceNumber.'&pOrdCob='.$sOrdenCobro.'&pObserva='.$sObservaciones.'&pDirRecep='.$sDireRecep; } else { $url = 'https://aptuslegal.app/aptusCFDIRF/'.$FormatoPDF.'.php?NomArchXML='.$NomArchXML.'&NomArchPDF='.$NomArchPDF; } $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); # 13.3 Envio del Archivos a Invoice de Zoho Books # Primero actualizamos el access_token de Books #== Datos y Variables para OAuth Token $boa_ClientId = $abdDatGen["B_OAuth_client_id"]; $boa_ClientSecret = $abdDatGen["B_OAuth_client_secret"]; $boa_RefreshToken = $abdDatGen["B_OAuth_refresh_token"]; $boa_GrantType = $abdDatGen["B_OAuth_grant_type"]; $boa_RedirectUri = $abdDatGen["B_OAuth_redirect_uri"]; $boa_AuthUrl = "https://accounts.zoho.com/oauth/v2/token"; #---------------------------------------------------------------- # JFA: 2021-06-12 # Obtenemos el access_token #---------------------------------------------------------------- // $Params = array( // "refresh_token" => $boa_RefreshToken, // "client_id" => $boa_ClientId, // "client_secret" => $boa_ClientSecret, // "redirect_uri" => $boa_RedirectUri, // "grant_type" => $boa_GrantType // ); // $curl = curl_init(); // curl_setopt($curl, CURLOPT_URL, $boa_AuthUrl); // curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); // curl_setopt($curl, CURLOPT_POSTFIELDS, $Params); // curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // $response = curl_exec($curl); // $array = json_decode($response); // $boa_access_token = $array->access_token; $boa_access_token = oauth($appOwner, 'ZBooks', $boa_RefreshToken, $boa_ClientId, $boa_ClientSecret, $boa_RedirectUri, $boa_GrantType, $boa_AuthUrl); //echo $access_token; # Primero el PDF $file_name_with_full_path = '/var/www/html/aptusCFDIRF/archs_cfdi/'.$NomArchPDF; $request_url = 'https://www.zohoapis.com/books/v3/invoices/'.$invoiceID.'/attachment'; 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); } $post = array( // 'authtoken' => $authtoken_ZB, 'organization_id' => $organi_id_ZB, 'can_send_in_mail' => 'true', 'attachment'=> $cFile); $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 ' . $boa_access_token)); $r = curl_exec($ch); $resultPDF = $r; curl_close ($ch); $ra = json_decode($r); //$resultPDF = $ra->code.' - '.$ra->message; //print_r($r); # Segundo el XML $file_name_with_full_path = '/var/www/html/aptusCFDIRF/archs_cfdi/'.$NomArchXML; $request_url = 'https://www.zohoapis.com/books/v3/invoices/'.$invoiceID.'/attachment'; 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); } $post = array( // 'authtoken' => $authtoken_ZB, 'organization_id' => $organi_id_ZB, 'can_send_in_mail' => 'true', 'attachment'=> $cFile); $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 ' . $boa_access_token)); $r = curl_exec($ch); //print_r($r); $resultXML = $r; curl_close ($ch); $ra = json_decode($r); //$resultXML = $ra->code.' - '.$ra->message; $file_name_with_full_path = '/var/www/html/aptusCFDIRF/archs_cfdi/'.$NomArchPDF; $resultPDF = uploadToCreator( $appOwner, $applnkname, $bRecId, $access_token, $file_name_with_full_path, 'File_CFDI_PDF/upload' ); if (! $resultPDF['success']) { // Regeneramos token forzando /f/ y reintentamos $access_token = getDonatelloToken($appOwner, true); // if (! $access_token) { // echo json_encode(['code'=>'401','message'=>'No se pudo regenerar token de Donatello para PDF']); // return; // } $resultPDF = uploadToCreator( $appOwner, $applnkname, $bRecId, $access_token, $file_name_with_full_path, 'File_CFDI_PDF/upload' ); // if (! $resultPDF['success']) { // echo json_encode(['code'=>'502','message'=>'Error al subir PDF a Zoho Creator']); // return; // } } # Segundo el XML $file_name_with_full_path = '/var/www/html/aptusCFDIRF/archs_cfdi/'.$NomArchXML; $resultXML = uploadToCreator( $appOwner, $applnkname, $bRecId, $access_token, $file_name_with_full_path, 'File_CFDI_XML/upload' ); if (! $resultXML['success']) { // Regeneramos token forzando /f/ y reintentamos $access_token = getDonatelloToken($appOwner, true); // if (! $access_token) { // echo json_encode(['code'=>'401','message'=>'No se pudo regenerar token de Donatello para XML']); // return; // } $resultXML = uploadToCreator( $appOwner, $applnkname, $bRecId, $access_token, $file_name_with_full_path, 'File_CFDI_XML/upload' ); // if (! $resultXML['success']) { // echo json_encode(['code'=>'502','message'=>'Error al subir XML a Zoho Creator']); // return; // } } #== Se elimina los archivos de trabajo unlink($SendaCFDI."PreCFDI-33_".$NoFac.".xml"); unlink($SendaCFDI."RespServ_".$NoFac.".xml"); unlink($filename); $j_array = array('code' => "200", 'message' => "Proceso de creacion de CFDI fue exitoso", 'version_timbre' => $version_timbre, 'cert_SAT' => $cert_SAT, 'tim_fecha' => $tim_fecha, 'tim_uuid' => $tim_uuid, 'tim_RfcPac' => $tim_RfcPac, 'sello_SAT' => $sello_SAT, 'sello_CFD' => $sello_CFD, 'RespBooks_FileXML' => $resultXML, 'RespBooks_FilePDF' => $resultPDF); $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){ $ImpTot = number_format($ImpTot, 4); // <== Se agregó el 30 de abril de 2017. $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 limpioCaracteresXML($cadena){ $search = array("<", ">", "&", "'"); $replace = array("<", ">", "&", "&apos"); $final = str_replace($search, $replace, $cadena); return $final; } /** * Llama a Zoho Creator y devuelve éxito sólo si HTTP 200 y Zoho code == 3000 */ function callZohoCreator(string $request_url, string $access_token): array { $ch = curl_init($request_url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => ['Authorization: Zoho-oauthtoken ' . $access_token], ]); $r = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $decoded = json_decode($r, true); $ok = ($httpCode === 200 && isset($decoded['code']) && $decoded['code'] === 3000); return ['success' => $ok, 'decoded' => $decoded]; } /** * Obtiene token de Donatello; si $force = true utiliza el endpoint /f/ */ function getDonatelloToken(string $appOwner, bool $force = false): ?string { $url = 'https://donatello.aptuslegal.app/oauth/token' . ($force ? '/f' : '') . '/' . $appOwner; $curl = curl_init($url); curl_setopt_array($curl, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => ['Authorization: Token e68d5a079f937ea29ad2ec5a5b105b75491a0e0c'], ]); $resp = curl_exec($curl); curl_close($curl); if (!$resp) return null; $data = json_decode($resp, true); return $data['access_token'] ?? null; } /** * Sube un archivo a Zoho Creator y devuelve ['success'=>bool, 'response'=>string]. * - $uploadEndpointSuffix: "File_CFDI_PDF/upload" o "File_CFDI_XML/upload" */ function uploadToCreator( string $appOwner, string $applnkname, string $bRecId, string $access_token, string $file_name_with_full_path, string $uploadEndpointSuffix ): array { // Construye el request URL $request_url = 'https://creator.zoho.com/api/v2/' . $appOwner . '/' . $applnkname . '/report/cfdi_I_query/' . $bRecId . '/' . $uploadEndpointSuffix; // Prepara el CURLFile if (function_exists('curl_file_create')) { $cFile = curl_file_create($file_name_with_full_path); } else { $cFile = '@' . realpath($file_name_with_full_path); } $post = ['file' => $cFile]; // Ejecuta la petición $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $request_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Zoho-oauthtoken ' . $access_token ]); $r = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // Consideramos éxito si HTTP 200 y Zoho code == 3000 $decoded = json_decode($r, true); $success = ($httpCode === 200 || $httpCode === 3000); return ['success' => $success, 'response' => $r]; }