From 7b8e5e5869a6967df3e270fbe3e72beb72616d94 Mon Sep 17 00:00:00 2001 From: David Obando Date: Wed, 30 Jul 2025 06:52:31 -0400 Subject: [PATCH 01/30] agregar consulta de comprobantes y corregir nombre de consulta de recibido --- api/contrib/consultar/consultar.php | 61 +++++++++++++++++++++++++++-- api/contrib/consultar/module.php | 16 +++++++- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/api/contrib/consultar/consultar.php b/api/contrib/consultar/consultar.php index a4f9299c..fcfd51e9 100644 --- a/api/contrib/consultar/consultar.php +++ b/api/contrib/consultar/consultar.php @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -function consutar() +function consultarRecepcion() { $curl = curl_init(); $clave = params_get('clave'); @@ -46,8 +46,63 @@ function consutar() CURLOPT_HTTPHEADER => array( "Authorization: Bearer " . params_get('token'), "Cache-Control: no-cache", - "Content-Type: application/x-www-form-urlencoded", - "Postman-Token: bf8dc171-5bb7-fa54-7416-56c5cda9bf5c" + "Content-Type: application/x-www-form-urlencoded" + ), + )); + + $response = curl_exec($curl); + $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $err = curl_error($curl); + curl_close($curl); + + if ($err) + { + $arrayResp = array( + "Status" => $status, + "to" => $apiTo, + "text" => $err + ); + return $arrayResp; + } + else + { + $response = json_decode($response); + return $response; + } +} + + +function consultarComprobante() +{ + $curl = curl_init(); + $clave = params_get('clave'); + + if ($clave == "" || strlen($clave) == 0) + return "La clave no puede ser en blanco"; + + $url = null; + if (params_get("client_id") == 'api-stag') + $url = "https://api-sandbox.comprobanteselectronicos.go.cr/recepcion/v1/comprobantes/"; + else if (params_get("client_id") == 'api-prod') + $url = "https://api.comprobanteselectronicos.go.cr/recepcion/v1/comprobantes/"; + + if ($url == null) + return "Ha ocurrido un error en el client_id."; + + curl_setopt_array($curl, array( + CURLOPT_URL => $url . $clave, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_SSL_VERIFYHOST => 0, + CURLOPT_SSL_VERIFYPEER => 0, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "GET", + CURLOPT_HTTPHEADER => array( + "Authorization: Bearer " . params_get('token'), + "Cache-Control: no-cache", + "Content-Type: application/x-www-form-urlencoded" ), )); diff --git a/api/contrib/consultar/module.php b/api/contrib/consultar/module.php index 0a86b3e6..4e77ca39 100644 --- a/api/contrib/consultar/module.php +++ b/api/contrib/consultar/module.php @@ -31,8 +31,20 @@ function consultar_init() { $paths = array( array( - 'r' => 'consultarCom', - 'action' => 'consutar', + 'r' => 'recepcion', + 'action' => 'consultarRecepcion', + 'access' => 'users_openAccess', + 'access_params' => 'accessName', + 'params' => array( + array("key" => "clave", "def" => "", "req" => true), + array("key" => "token", "def" => "", "req" => true), + array("key" => "client_id", "def" => "", "req" => true) + ), + 'file' => 'consultar.php' + ), + array( + 'r' => 'comprobante', + 'action' => 'consultarComprobante', 'access' => 'users_openAccess', 'access_params' => 'accessName', 'params' => array( From 3dea3c1cd7da3e312f2a433523a4cb349f8a021a Mon Sep 17 00:00:00 2001 From: David Obando Date: Wed, 30 Jul 2025 06:54:26 -0400 Subject: [PATCH 02/30] ajustar descripcion del README en el modulo de consulta --- api/contrib/consultar/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/contrib/consultar/README.md b/api/contrib/consultar/README.md index 6c4d165c..3be30168 100644 --- a/api/contrib/consultar/README.md +++ b/api/contrib/consultar/README.md @@ -1,7 +1,8 @@ -# Modulo para hacer comprobar estado de los comprobantes +# Modulo para hacer comprobar estado de los comprobantes y su estado de recepcion + Se envia: * `w` : consultar -* `r` : consultarCom +* `r` : recepcion o comprobante * `clave` : Clave numerica del comprobante -* `token`: Token funcional para enviar en el header +* `token`: Token obtenido de Hacienda From 6e2537e5b5243d0798ebee95d6e5765b8fdf8430 Mon Sep 17 00:00:00 2001 From: David Obando Date: Sat, 23 Aug 2025 09:00:38 -0400 Subject: [PATCH 03/30] feat: agregar validacion al crear FE --- api/contrib/facturador/companny_user.php | 6 +- api/contrib/genXML/genXML.php | 8 +- api/core/tools.php | 31 +++++--- api/modules/users/module.php | 2 +- api/tools/ValidadorXML.php | 93 ++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 16 deletions(-) create mode 100644 api/tools/ValidadorXML.php diff --git a/api/contrib/facturador/companny_user.php b/api/contrib/facturador/companny_user.php index ff9996c3..790086e3 100644 --- a/api/contrib/facturador/companny_user.php +++ b/api/contrib/facturador/companny_user.php @@ -134,7 +134,7 @@ function companny_users_generateSessionKey($idUser, $idMasterUser) { $q = sprintf("delete from " . db_escape($idMasterUser) . "_master_sessions where idUser='" . db_escape($idUser) . "'"); db_query($q, 0); - $sessionKey = password_hash(time() * rand(0, 1000), PASSWORD_DEFAULT); + $sessionKey = password_hash(time() * rand(0, 1000), PASSWORD_ARGON2ID); $q = sprintf("INSERT INTO " . db_escape($idMasterUser) . "_master_sessions (idUser, sessionKey, ip, lastAccess) " . "VALUES('%s', '%s', '%s', '%s')", db_escape($idUser), db_escape($sessionKey), db_escape($_SERVER['REMOTE_ADDR']), time()); @@ -409,7 +409,7 @@ function companny_users_getMyDetails() { * Helper function to actually create a new user and register it in the db */ function _companny_users_register($compannyUserDets, $idMasterUser) { - $pwd = password_hash($compannyUserDets['pwd'], PASSWORD_DEFAULT); + $pwd = password_hash($compannyUserDets['pwd'], PASSWORD_ARGON2ID); $q = sprintf("INSERT INTO " . db_escape($idMasterUser) . "_master_users (idMasterUser,fullName, userName, email, about, country, status, timestamp, lastAccess, pwd, avatar,settings) VALUES('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", db_escape($idMasterUser), db_escape($compannyUserDets['fullName']), db_escape($compannyUserDets['userName']), db_escape($compannyUserDets['email']), db_escape(addslashes($compannyUserDets['about'])), db_escape($compannyUserDets['country']), db_escape($compannyUserDets['status']), db_escape($compannyUserDets['timestamp']), db_escape($compannyUserDets['lastAccess']), db_escape($pwd), db_escape($compannyUserDets['avatar']), db_escape($compannyUserDets['settings']) ); @@ -511,7 +511,7 @@ function companny_users_recoverPwd() { $temp = rand(0, 1000) + time(); - $compannyUser->pwd = password_hash($temp, PASSWORD_DEFAULT); + $compannyUser->pwd = password_hash($temp, PASSWORD_ARGON2ID); grace_debug("New tmp pwd: " . $compannyUser->pwd); # Update account diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index b197e2c0..76a75bbe 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -30,6 +30,7 @@ const RECEPTOROTRASSENASEXTRANJEROMAXSIZE = 300; const EMAIL_REGEX = "/^\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*$/"; +tools_useTool('ValidadorXML.php'); /* * ************************************************** */ /* Funcion para generar XML */ @@ -1028,9 +1029,14 @@ function genXMLFe() $xmlString .= ' '; + + // Validar XML usando el XSD correspondiente + $r = ValidadorXML::validateXml($xmlString, $consecutivo); + $arrayResp = array( "clave" => $clave, - "xml" => base64_encode($xmlString) + "xml" => base64_encode($xmlString), + "validacion" => $r ); return $arrayResp; diff --git a/api/core/tools.php b/api/core/tools.php index 64c4d821..eede23ff 100644 --- a/api/core/tools.php +++ b/api/core/tools.php @@ -49,17 +49,26 @@ function tools_reply($response, $killMe = false) } if (is_array($response) && isset($response['Status'])) { - switch ($response['Status']) { - case 'error': - http_response_code(500); - $killMe = true; - break; - case 'ok': - http_response_code(200); - break; - default: - http_response_code(400); - $killMe = true; + // propagar l codigo de estado HTTP cuando se recibe error de Hacienda + if (is_numeric($response['Status'])) { + http_response_code($response['Status']); + $killMe = $response['Status'] >= 299; + unset($response['text'][0]); + + } else { + switch ($response['Status']) { + case 'error': + http_response_code(500); + $killMe = true; + break; + case 'ok': + http_response_code(200); + break; + default: + http_response_code(400); + $killMe = true; + } + } $response = $response['text']; } diff --git a/api/modules/users/module.php b/api/modules/users/module.php index 597f7113..788184f4 100644 --- a/api/modules/users/module.php +++ b/api/modules/users/module.php @@ -380,7 +380,7 @@ function users_generateSessionKey($idUser) db_query($q, 0); modules_loader("crypto", "crypto.php"); - $sessionKey = crypto_encrypt(password_hash(time() * rand(0, 1000), PASSWORD_DEFAULT)); + $sessionKey = crypto_encrypt(password_hash(time() * rand(0, 1000), PASSWORD_ARGON2ID)); $q = sprintf("INSERT INTO sessions (idUser, sessionKey, ip, lastAccess) " . "VALUES('%s', '%s', '%s', '%s')", db_escape($idUser), $sessionKey, db_escape($_SERVER['REMOTE_ADDR']), time()); diff --git a/api/tools/ValidadorXML.php b/api/tools/ValidadorXML.php new file mode 100644 index 00000000..7e90726c --- /dev/null +++ b/api/tools/ValidadorXML.php @@ -0,0 +1,93 @@ +code; + + switch ($error->level) { + case LIBXML_ERR_WARNING: + $return["tipo"] = "warning"; + break; + case LIBXML_ERR_ERROR: + $return["tipo"] = "error"; + break; + case LIBXML_ERR_FATAL: + $return["tipo"] = "fatal_error"; + break; + } + $return["mensaje"] = $error->message; + //$return["archivo"] = $error->file; + //$return["linea"] = $error->line; + + return $return; + } + + private static function libxml_display_errors() + { + $errors = libxml_get_errors(); + libxml_clear_errors(); + $str_errors = []; + foreach ($errors as $error) { + $str_errors[] = ValidadorXML::libxml_display_error($error); + } + + return $str_errors; + } + + /** + * Valida el XML contra el XSD + * @param string $contenido_xml - XML sin firmar en base64 + * @param string $tipo_doc - tipo de documento a validar + * @return type + */ + public static function validateXml($contenido_xml, $consecutivo, $version = "4.4") + { + $tipo_doc = substr($consecutivo, 8, 2); + $schemas = [ + "01" => "xsd/FacturaElectronica_V$version.xsd", + "02" => "xsd/FacturaCompra_V$version.xsd", + "03" => "xsd/NotaCreditoElectronica_V$version.xsd", + "04" => "xsd/TiqueteElectronico_V$version.xsd", + "05" => "xsd/MensajeHacienda_V$version.xsd", + ]; + + if (empty($contenido_xml) || empty($tipo_doc)) { + return (object) [ + "status" => "error", + "message" => "Error: falta el contenido del XML o el tipo de documento", + ]; + } + + if (!array_key_exists($tipo_doc, $schemas)) { + return (object) [ + "status" => "error", + "message" => "Error: no se tiene un esquema para el tipo $tipo_doc v$version", + ]; + } + + if (!file_exists($schemas[$tipo_doc])) { + return (object) [ + "status" => "error", + "message" => "Error: no se encuentra el esquema " . $schemas[$tipo_doc] . " definido para el tipo $tipo_doc, v$version", + ]; + } + + libxml_use_internal_errors(true); + $xml = new DOMDocument(); + $xml->loadXML($contenido_xml); + + if (!$xml->schemaValidate($schemas[$tipo_doc])) { + return (object) [ + "status" => "ok", + "schema" => $schemas[$tipo_doc], + "message" => ValidadorXML::libxml_display_errors() + ]; + } + } +} From 532ca2ac6e7b48dc53b18d02cd28fc98f5ac0ce7 Mon Sep 17 00:00:00 2001 From: David Obando Date: Sat, 23 Aug 2025 09:08:54 -0400 Subject: [PATCH 04/30] feat: agregar validacion al crear FE y demas documentos --- api/contrib/genXML/genXML.php | 53 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 76a75bbe..1b3ef013 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -1030,16 +1030,12 @@ function genXMLFe() $xmlString .= ' '; - // Validar XML usando el XSD correspondiente - $r = ValidadorXML::validateXml($xmlString, $consecutivo); - - $arrayResp = array( + return array( "clave" => $clave, "xml" => base64_encode($xmlString), - "validacion" => $r + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - return $arrayResp; } function genXMLNC() @@ -1922,12 +1918,11 @@ function genXMLNC() $xmlString .= ' '; - $arrayResp = array( + return array( "clave" => $clave, - "xml" => base64_encode($xmlString) + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - - return $arrayResp; } function genXMLND() @@ -2808,12 +2803,11 @@ function genXMLND() $xmlString .= ' '; - $arrayResp = array( + return array( "clave" => $clave, - "xml" => base64_encode($xmlString) + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - - return $arrayResp; } function genXMLTE() @@ -3678,12 +3672,13 @@ function genXMLTE() $xmlString .= ' '; - $arrayResp = array( + + return array( "clave" => $clave, - "xml" => base64_encode($xmlString) + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - - return $arrayResp; + } function genXMLMr() @@ -3735,12 +3730,12 @@ function genXMLMr() ' . $numeroConsecutivoReceptor . ''; $xmlString .= ''; - $arrayResp = array( + + return array( "clave" => $clave, "xml" => base64_encode($xmlString) ); - return $arrayResp; } function genXMLFec() @@ -4484,12 +4479,13 @@ function genXMLFec() $xmlString .= ' '; - $arrayResp = array( + + return array( "clave" => $clave, - "xml" => base64_encode($xmlString) + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - - return $arrayResp; + } function genXMLFee() @@ -5328,12 +5324,13 @@ function genXMLFee() $xmlString .= ' '; - $arrayResp = array( + + return array( "clave" => $clave, - "xml" => base64_encode($xmlString) + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - - return $arrayResp; + } From c1ee1e9becedafdf39c8ee54e8f332fb7d5a7c1a Mon Sep 17 00:00:00 2001 From: David Obando Date: Sun, 24 Aug 2025 19:09:00 -0400 Subject: [PATCH 05/30] validacion general de campos para factura electronica, mejorar respuestas de errores --- api/contrib/consultar/consultar.php | 17 +- api/contrib/genXML/genXML.php | 485 +-- api/contrib/signXML/Firmadohaciendacr.php | 36 +- api/core/tools.php | 9 +- api/tools/ValidadorXML.php | 32 +- www/xsd/FacturaElectronica_V4.4.xsd | 3251 ++++++++++++++++++++- 6 files changed, 3632 insertions(+), 198 deletions(-) diff --git a/api/contrib/consultar/consultar.php b/api/contrib/consultar/consultar.php index fcfd51e9..72c14d77 100644 --- a/api/contrib/consultar/consultar.php +++ b/api/contrib/consultar/consultar.php @@ -66,7 +66,22 @@ function consultarRecepcion() } else { - $response = json_decode($response); + $response = json_decode($response); + + if(isset($response->{'respuesta-xml'})) { + $xmlRespuesta = base64_decode($response->{'respuesta-xml'}); + $xmlRespuesta = preg_replace('/>\s+<', $xmlRespuesta); + $startLength = strlen("tiene los siguientes errores: ")+7; + $startPos = strpos($xmlRespuesta, "tiene los siguientes errores: ", 0); + if(!$startPos) { + // si no hay errores, retornar la respuesta normal + return $response; + } + $endPos = strpos($xmlRespuesta, "", $startPos); + $detalleMensaje = substr($xmlRespuesta, $startPos+$startLength, $endPos -($startPos+$startLength+1)); + $separadorMensaje = " \n"; + $response->{'DetalleMensaje'} = explode($separadorMensaje,trim($detalleMensaje,$separadorMensaje)); + } return $response; } } diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 1b3ef013..84341001 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -32,6 +32,106 @@ tools_useTool('ValidadorXML.php'); +const AUTO_CALCULAR = ["totalServGravados", "totalServExentos", "totalServExonerado", "totalServNoSujeto", "totalMercanciasGravadas", "totalMercanciasExentas", "totalMercExonerada", "totalMercNoSujeta", "totalGravado", "totalExento", "totalExonerado", "totalNoSujeto", "totalVenta", "totalDescuentos", "totalVentaNeta","totalImpuesto"]; + +/** + * Devuelve true si todas las claves existen en el array + */ +function array_all(array $keys, array $array) { + foreach ($keys as $key) { + if (!array_key_exists($key, $array)) { + return false; + } + } + return true; +} + +/** + * Devuelve true si al menos una de las claves existe en el array + */ +function array_any(array $keys, array $array) { + foreach ($keys as $key) { + if (array_key_exists($key, $array)) { + return true; + } + } + return false; +} + +/** + * Valida las reglas conocidas de una linea del detalle para evitar rechazos + * + * @param int $numLinea + * @param object $detallesLinea + * @return void + */ +function validarLinea($numLinea, $d) { + $d = (array)$d; + + if(isset($d['baseImponible']) && !isset($d['impuesto'])) { + tools_reply([ + "Status" => 400, + "text" => "Detalle de líneas $numLinea: cuando se envía el campo 'baseImponible' es obligatorio enviar también el campo 'impuesto'", + ], true); + } +} + +/** + * Actualiza los campos auto calculables usando los detalles de la linea + * + * @param int $numLinea + * @param object $detallesLinea + * @param array &$calculados + * @return void + */ +function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) { + + if(!isset($calculados[$numLinea])) { + $calculados[$numLinea] = [ + "impuestoNeto" => 0, + "montoTotalLinea" => 0, // sumatoria de los campos “Subtotal”, “Impuesto Neto”. + "baseImponible" => 0, + "montoTotal" => 0, // multiplicar el campo cantidad por el campo precio unitario + "subTotal" => 0 + ]; + } + + // Se obtiene de la resta del campo monto total menos monto de descuento concedido + $calculados[$numLinea]['montoTotal'] = $d->precioUnitario * $d->cantidad; + $calculados[$numLinea]['subTotal'] = $calculados[$numLinea]['montoTotal'] - $totalDescuentos; + + // Se calcula de la siguiente manera: se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. + + $calculados[$numLinea]['montoTotalLinea'] += ($calculados[$numLinea]['subTotal'] + $calculados[$numLinea]['montoTotalLinea']); + + // impuesto.Codigo = 02, 04, 05, 12 no considerados aun + // impuesto.codigo = 01 + tarifa + if(!isset($d->impuesto) || !is_array($d->impuesto)) { + tools_reply("Al auto-calcular baseImponible, detalle de líneas $numLinea: es obligatorio enviar el campo 'impuesto'", true); + } + + $calculados[$numLinea]['baseImponible'] = $calculados[$numLinea]['subTotal']; + + /* + baseImponible: + Se obtiene de la suma entre el campo "Subtotal", más + el impuesto selectivo de consumo (02), + el Impuesto específico de Bebidas Alcohólicas (04), + el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05) y + el impuesto al cemento (12), cuando corresponda. + Este campo se podrá editar cuando se seleccione + en el campo "IVA cobrado a nivel de fábrica" el Código 01 + o en el campo de "Código del impuesto" el código 07. + */ + + foreach(AUTO_CALCULAR as $field) { + if(!isset($calculados[$field])) { + $calculados[$field] = 0; + } + } + +} + /* * ************************************************** */ /* Funcion para generar XML */ /* * ************************************************** */ @@ -86,17 +186,17 @@ function genXMLFe() $tipoCambio = params_get("tipo_cambio"); $totalServGravados = params_get("total_serv_gravados"); $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerados = params_get("total_serv_exonerados"); + $totalServExonerado = params_get("total_serv_exonerados"); $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); $totalMercExonerada = params_get("total_merc_exonerada"); $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = params_get("total_gravados"); + $totalGravado = params_get("total_gravados"); $totalExento = params_get("total_exento"); $totalExonerado = params_get("total_exonerado"); $totalNoSujeto = params_get("total_no_sujeto"); - $totalVentas = params_get("total_ventas"); + $totalVenta = params_get("total_ventas"); $totalDescuentos = params_get("total_descuentos"); $totalVentasNeta = params_get("total_ventas_neta"); $totalImp = params_get("total_impuestos"); @@ -162,6 +262,13 @@ function genXMLFe() //Delimita el array a solo 4 elementos $mediosPago = array_slice($mediosPago, 0, 4); } + } else { + if (!in_array($condVenta, ['02', '08', '10'], true)) { + tools_reply([ + "Status" => 400, + "text" => "El campo 'medios_pago' es obligatorio cuando 'condicion_venta' no es '02', '08' o '10'", + ], true); + } } $xmlString = ' @@ -433,10 +540,11 @@ function genXMLFe() */ $l = 1; - + $calculados = []; + foreach ($detalles as $d) { - foreach (["codigoCABYS","subTotal","impuestoAsumidoEmisorFabrica","impuestoNeto"] as $requiredField) { + foreach (["codigoCABYS"] as $requiredField) { if (!isset($d->{$requiredField}) || $d->{$requiredField} === '') { tools_reply("Se requiere el campo $requiredField en el detalle #$l", true); } @@ -484,8 +592,7 @@ function genXMLFe() $xmlString .= ' ' . $d->unidadMedidaComercial . ''; } - $xmlString .= ' - ' . $d->detalle . ''; + $xmlString .= '' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { $xmlString .= '' . $d->numeroVINoSerie . ''; } @@ -580,10 +687,9 @@ function genXMLFe() $xmlString .= ''; } - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; + + $totalDescuentos = 0; if (isset($d->descuento) && !empty($d->descuento)) { $descuentoArray = (array)$d->descuento; @@ -599,8 +705,7 @@ function genXMLFe() isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" ) { - $xmlString .= ' - + $xmlString .= ' ' . $c['montoDescuento'] . ' ' . $c['codigoDescuento'] . ''; // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo @@ -618,22 +723,25 @@ function genXMLFe() ) { $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; } - $xmlString .= ' - '; + $xmlString .= ''; + $totalDescuentos += floatval($c['montoDescuento']); } } } - $xmlString .= '' . $d->subTotal . ''; + // Calcular los valores que se pueden derivar de los datos ingresados + autoCalcularLinea($l, $d, $totalDescuentos, $calculados); + + $xmlString .= '' . $d->precioUnitario . ''; + $xmlString .= '' . $calculados[$l]['montoTotal'] . ''; + $xmlString .= '' . $calculados[$l]['subTotal'] . ''; if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { $xmlString .= '' . $d->IVACobradoFabrica . ''; } - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } - + $xmlString .= '' . $calculados[$l]['baseImponible'] . ''; + if (isset($d->impuesto) && $d->impuesto != "") { foreach ($d->impuesto as $i) { $xmlString .= ' @@ -648,13 +756,31 @@ function genXMLFe() $xmlString .= '' . $i->codigoImpuestoOtro . ''; } + // calcular el monto de impuesto si no se proporciona usando el codigo y la tarifa + if($i->codigo==="01" && isset($i->codigoTarifa)) { + + $impuestoPorTarifa = [ + "01" => 0, // 0% (Artículo 32, num 1, RLIVA) + "02" => 0.01, // Tarifa reducida 1% + "03" => 0.02, // Tarifa reducida 2% + "04" => 0.04, // Tarifa reducida 4% + "05" => 0, // Transitorio 0% + "06" => 0.04, // Transitorio 4% + "07" => 0.08, // Tarifa transitoria 8% + "08" => 0.13, // Tarifa general 13% + "09" => 0.05, // Tarifa reducida 0.5% + "10" => 0, // Tarifa Exenta + "11" => 0 // Tarifa 0% sin derecho a crédito + ]; + $calculados[$l]['tarifa'] = round($impuestoPorTarifa[$i->codigoTarifa], 2)*100; + $calculados[$l]['monto'] = round($calculados[$l]['baseImponible'] * $impuestoPorTarifa[$i->codigoTarifa], 2); + } + if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { $xmlString .= '' . $i->codigoTarifa . ''; } - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } + $xmlString .= '' . $calculados[$l]['tarifa'] . ''; if (isset($i->factorIVA) && $i->factorIVA != "") { $xmlString .= '' . $i->factorIVA . ''; @@ -685,6 +811,14 @@ function genXMLFe() } $xmlString .= ''; } + + if (!isset($i->monto)) { + $i->{"monto"} = $calculados[$l]['monto']; + } else { + if($i->monto != $calculados[$l]['monto']) { + tools_reply("El monto del impuesto no coincide con el calculado para la línea $l, Calculado: " . $calculados[$l]['monto'], true); + } + } $xmlString .= '' . $i->monto . ''; @@ -714,17 +848,63 @@ function genXMLFe() } $xmlString .= ''; + + if(isset($i->codigo) && isset($i->monto)) { + // Se suma el impuesto neto a totalImpuestos + $calculados[$l]['impuestoNeto'] += $i->monto; + + if($i->codigoTarifa === "10" || $i->codigoTarifa === "11") { + // Exento + $resumenKeys = [ + "servicio" => "totalServExentos", + "mercancia" => "totalMercanciasExentas", + ]; + $calculados['totalExento'] += $calculados[$l]['baseImponible']; + } else { + // Gravado + $resumenKeys = [ + "servicio" => "totalServGravados", + "mercancia" => "totalMercanciasGravadas", + ]; + $calculados['totalGravado'] += $calculados[$l]['baseImponible']; + } + + // Se suma el impuesto neto a totalImpuestos + // 4.4 se basa en los cabys para saber que es un servicio o mercaderia, + // los codigos que empiezan de 0 a 4 son mercaderias y los que empiezan de 5 a 9 son servicios + if(isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5','6','7','8','9'], true)) { + // Servicio + $calculados[$resumenKeys['servicio']] += $calculados[$l]['baseImponible']; + } else { + // Mercancia + $calculados[$resumenKeys['mercancia']] += $calculados[$l]['baseImponible']; + } + + $calculados['totalImpuesto'] += $i->monto; + $calculados['totalVenta'] += $calculados[$l]['baseImponible']; + + } } } - $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; - $xmlString .= '' . $d->impuestoNeto . ''; - $xmlString .= '' . $d->montoTotalLinea . ''; + if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { + $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; + } else { + $xmlString .= '0'; + } + + $xmlString .= '' . $calculados[$l]['impuestoNeto'] . ''; + + $xmlString .= '' . $calculados[$l]['montoTotalLinea'] . ''; $xmlString .= ''; + + validarLinea($l, $d); + $l++; } $xmlString .= ''; + //OtrosCargos if (isset($otrosCargos) && $otrosCargos != "") { foreach ($otrosCargos as $o) { @@ -776,77 +956,25 @@ function genXMLFe() '; } - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerados != '') { - $xmlString .= ' - ' . $totalServExonerados . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercGravadas != '') { - $xmlString .= ' - ' . $totalMercGravadas . ''; - } - - if ($totalMercExentas != '') { - $xmlString .= ' - ' . $totalMercExentas . ''; - } + // Calcular totalVentaNeta + $calculados['totalVentaNeta'] = $calculados['totalVenta'] - $calculados['totalDescuentos']; - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravados != '') { - $xmlString .= ' - ' . $totalGravados . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; + // totalComprobante: Se obtiene de la suma de los campos "total venta neta", "monto total del impuesto" y "total otros cargos" menos "total IVA devuelto", en caso de contar con dichos campos. + $calculados['totalComprobante'] = $calculados['totalVentaNeta'] + $calculados['totalImpuesto']; + if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { + $calculados['totalComprobante'] += $totalOtrosCargos; } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; + if ($totalIVADevuelto != '') { + $calculados['totalComprobante'] -= $totalIVADevuelto; } - $xmlString .= ' - ' . $totalVentas . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; + // agrega los campos del resumen que esten en el array calculados y no esten vacios + foreach (AUTO_CALCULAR as $campoResumen) { + if($calculados[$campoResumen]!='') { + $xmlString .= '<' . ucfirst($campoResumen) . '>' . $calculados[$campoResumen] . ''; + } } - $xmlString .= ' - ' . $totalVentasNeta . ''; - // Add logic for TotalDesgloseImpuesto if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { foreach ($totalDesgloseImpuesto as $impuesto) { @@ -865,11 +993,6 @@ function genXMLFe() } } - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - if ($totalImpAsumidoEmisorFabrica != '') { $xmlString .= ' ' . $totalImpAsumidoEmisorFabrica . ''; @@ -886,6 +1009,7 @@ function genXMLFe() } if (isset($mediosPago) && !empty($mediosPago)) { + $totalMediosPago = 0; foreach ($mediosPago as $o) { $xmlString .= ' '; @@ -906,11 +1030,15 @@ function genXMLFe() } $xmlString .= ''; + $totalMediosPago += floatval($o->totalMedioPago); + } + if ($totalMediosPago != $calculados['totalComprobante']) { + tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: $totalMediosPago, Total Comprobante: " . $calculados['totalComprobante'], true); } } $xmlString .= ' - ' . $totalComprobante . ' + ' . $calculados['totalComprobante'] . ' '; if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { @@ -1007,7 +1135,6 @@ function genXMLFe() $xmlString .= ''; - // XML Resultante // // Texto opcional 1 @@ -1030,12 +1157,25 @@ function genXMLFe() $xmlString .= ' '; + // eliminar espacios en blanco para reducir el tamaño del XML + $xmlString = preg_replace('/>\s+<', $xmlString); + + $validacion = ValidadorXML::validateXml($xmlString, $consecutivo); + + if($validacion && $validacion->status === 'error') { + tools_reply([ + "Status" => 400, + "text" => array( + "clave" => $clave, + "validacion" => $validacion + ) + ], true); + } + return array( "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) + "xml" => base64_encode($xmlString) ); - } function genXMLNC() @@ -1087,19 +1227,20 @@ function genXMLNC() $plazoCredito = params_get("plazo_credito"); $codMoneda = params_get("cod_moneda"); $tipoCambio = params_get("tipo_cambio"); + $totalServGravados = params_get("total_serv_gravados"); $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerados = params_get("total_serv_exonerados"); + $totalServExonerado = params_get("total_serv_exonerados"); $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); $totalMercExonerada = params_get("total_merc_exonerada"); $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = params_get("total_gravados"); + $totalGravado = params_get("total_gravado"); $totalExento = params_get("total_exento"); $totalExonerado = params_get("total_exonerado"); $totalNoSujeto = params_get("total_no_sujeto"); - $totalVentas = params_get("total_ventas"); + $totalVenta = params_get("total_venta"); $totalDescuentos = params_get("total_descuentos"); $totalVentasNeta = params_get("total_ventas_neta"); $totalImp = params_get("total_impuestos"); @@ -1669,9 +1810,9 @@ function genXMLNC() ' . $totalServExentos . ''; } - if ($totalServExonerados != '') { + if ($totalServExonerado != '') { $xmlString .= ' - ' . $totalServExonerados . ''; + ' . $totalServExonerado . ''; } if ($totalServNoSujeto != '') { @@ -1679,14 +1820,14 @@ function genXMLNC() ' . $totalServNoSujeto . ''; } - if ($totalMercGravadas != '') { + if ($totalMercanciasGravadas != '') { $xmlString .= ' - ' . $totalMercGravadas . ''; + ' . $totalMercanciasGravadas . ''; } - if ($totalMercExentas != '') { + if ($totalMercanciasExentas != '') { $xmlString .= ' - ' . $totalMercExentas . ''; + ' . $totalMercanciasExentas . ''; } if ($totalMercExonerada != '') { @@ -1699,9 +1840,9 @@ function genXMLNC() ' . $totalMercNoSujeta . ''; } - if ($totalGravados != '') { + if ($totalGravado != '') { $xmlString .= ' - ' . $totalGravados . ''; + ' . $totalGravado . ''; } if ($totalExento != '') { @@ -1720,7 +1861,7 @@ function genXMLNC() } $xmlString .= ' - ' . $totalVentas . ''; + ' . $totalVenta . ''; if ($totalDescuentos != '') { $xmlString .= ' @@ -1976,17 +2117,17 @@ function genXMLND() $tipoCambio = params_get("tipo_cambio"); $totalServGravados = params_get("total_serv_gravados"); $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerados = params_get("total_serv_exonerados"); + $totalServExonerado = params_get("total_serv_exonerados"); $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); $totalMercExonerada = params_get("total_merc_exonerada"); $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = params_get("total_gravados"); + $totalGravado = params_get("total_gravados"); $totalExento = params_get("total_exento"); $totalExonerado = params_get("total_exonerado"); $totalNoSujeto = params_get("total_no_sujeto"); - $totalVentas = params_get("total_ventas"); + $totalVenta = params_get("total_ventas"); $totalDescuentos = params_get("total_descuentos"); $totalVentasNeta = params_get("total_ventas_neta"); $totalImp = params_get("total_impuestos"); @@ -2554,9 +2695,9 @@ function genXMLND() ' . $totalServExentos . ''; } - if ($totalServExonerados != '') { + if ($totalServExonerado != '') { $xmlString .= ' - ' . $totalServExonerados . ''; + ' . $totalServExonerado . ''; } if ($totalServNoSujeto != '') { @@ -2564,14 +2705,14 @@ function genXMLND() ' . $totalServNoSujeto . ''; } - if ($totalMercGravadas != '') { + if ($totalMercanciasGravadas != '') { $xmlString .= ' - ' . $totalMercGravadas . ''; + ' . $totalMercanciasGravadas . ''; } - if ($totalMercExentas != '') { + if ($totalMercanciasExentas != '') { $xmlString .= ' - ' . $totalMercExentas . ''; + ' . $totalMercanciasExentas . ''; } if ($totalMercExonerada != '') { @@ -2584,9 +2725,9 @@ function genXMLND() ' . $totalMercNoSujeta . ''; } - if ($totalGravados != '') { + if ($totalGravado != '') { $xmlString .= ' - ' . $totalGravados . ''; + ' . $totalGravado . ''; } if ($totalExento != '') { @@ -2605,7 +2746,7 @@ function genXMLND() } $xmlString .= ' - ' . $totalVentas . ''; + ' . $totalVenta . ''; if ($totalDescuentos != '') { $xmlString .= ' @@ -2860,17 +3001,17 @@ function genXMLTE() $tipoCambio = params_get("tipo_cambio"); $totalServGravados = params_get("total_serv_gravados"); $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerados = params_get("total_serv_exonerados"); + $totalServExonerado = params_get("total_serv_exonerados"); $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); $totalMercExonerada = params_get("total_merc_exonerada"); $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = params_get("total_gravados"); + $totalGravado = params_get("total_gravados"); $totalExento = params_get("total_exento"); $totalExonerado = params_get("total_exonerado"); $totalNoSujeto = params_get("total_no_sujeto"); - $totalVentas = params_get("total_ventas"); + $totalVenta = params_get("total_ventas"); $totalDescuentos = params_get("total_descuentos"); $totalVentasNeta = params_get("total_ventas_neta"); $totalImp = params_get("total_impuestos"); @@ -3424,9 +3565,9 @@ function genXMLTE() ' . $totalServExentos . ''; } - if ($totalServExonerados != '') { + if ($totalServExonerado != '') { $xmlString .= ' - ' . $totalServExonerados . ''; + ' . $totalServExonerado . ''; } if ($totalServNoSujeto != '') { @@ -3434,14 +3575,14 @@ function genXMLTE() ' . $totalServNoSujeto . ''; } - if ($totalMercGravadas != '') { + if ($totalMercanciasGravadas != '') { $xmlString .= ' - ' . $totalMercGravadas . ''; + ' . $totalMercanciasGravadas . ''; } - if ($totalMercExentas != '') { + if ($totalMercanciasExentas != '') { $xmlString .= ' - ' . $totalMercExentas . ''; + ' . $totalMercanciasExentas . ''; } if ($totalMercExonerada != '') { @@ -3454,9 +3595,9 @@ function genXMLTE() ' . $totalMercNoSujeta . ''; } - if ($totalGravados != '') { + if ($totalGravado != '') { $xmlString .= ' - ' . $totalGravados . ''; + ' . $totalGravado . ''; } if ($totalExento != '') { @@ -3475,7 +3616,7 @@ function genXMLTE() } $xmlString .= ' - ' . $totalVentas . ''; + ' . $totalVenta . ''; if ($totalDescuentos != '') { $xmlString .= ' @@ -3678,7 +3819,6 @@ function genXMLTE() "xml" => base64_encode($xmlString), "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - } function genXMLMr() @@ -3735,7 +3875,6 @@ function genXMLMr() "clave" => $clave, "xml" => base64_encode($xmlString) ); - } function genXMLFec() @@ -3788,17 +3927,17 @@ function genXMLFec() $tipoCambio = params_get("tipo_cambio"); $totalServGravados = params_get("total_serv_gravados"); $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerados = params_get("total_serv_exonerados"); + $totalServExonerado = params_get("total_serv_exonerados"); $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); $totalMercExonerada = params_get("total_merc_exonerada"); $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = params_get("total_gravados"); + $totalGravado = params_get("total_gravados"); $totalExento = params_get("total_exento"); $totalExonerado = params_get("total_exonerado"); $totalNoSujeto = params_get("total_no_sujeto"); - $totalVentas = params_get("total_ventas"); + $totalVenta = params_get("total_ventas"); $totalDescuentos = params_get("total_descuentos"); $totalVentasNeta = params_get("total_ventas_neta"); $totalImp = params_get("total_impuestos"); @@ -4236,9 +4375,9 @@ function genXMLFec() ' . $totalServExentos . ''; } - if ($totalServExonerados != '') { + if ($totalServExonerado != '') { $xmlString .= ' - ' . $totalServExonerados . ''; + ' . $totalServExonerado . ''; } if ($totalServNoSujeto != '') { @@ -4246,14 +4385,14 @@ function genXMLFec() ' . $totalServNoSujeto . ''; } - if ($totalMercGravadas != '') { + if ($totalMercanciasGravadas != '') { $xmlString .= ' - ' . $totalMercGravadas . ''; + ' . $totalMercanciasGravadas . ''; } - if ($totalMercExentas != '') { + if ($totalMercanciasExentas != '') { $xmlString .= ' - ' . $totalMercExentas . ''; + ' . $totalMercanciasExentas . ''; } if ($totalMercExonerada != '') { @@ -4266,9 +4405,9 @@ function genXMLFec() ' . $totalMercNoSujeta . ''; } - if ($totalGravados != '') { + if ($totalGravado != '') { $xmlString .= ' - ' . $totalGravados . ''; + ' . $totalGravado . ''; } if ($totalExento != '') { @@ -4287,7 +4426,7 @@ function genXMLFec() } $xmlString .= ' - ' . $totalVentas . ''; + ' . $totalVenta . ''; if ($totalDescuentos != '') { $xmlString .= ' @@ -4485,7 +4624,6 @@ function genXMLFec() "xml" => base64_encode($xmlString), "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - } function genXMLFee() @@ -4530,11 +4668,11 @@ function genXMLFee() $totalServGravados = params_get("total_serv_gravados"); $totalServExentos = params_get("total_serv_exentos"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); - $totalGravados = params_get("total_gravados"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); + $totalGravado = params_get("total_gravados"); $totalExento = params_get("total_exento"); - $totalVentas = params_get("total_ventas"); + $totalVenta = params_get("total_ventas"); $totalDescuentos = params_get("total_descuentos"); $totalVentasNeta = params_get("total_ventas_neta"); $totalImp = params_get("total_impuestos"); @@ -5068,19 +5206,19 @@ function genXMLFee() ' . $totalServExentos . ''; } - if ($totalMercGravadas != '') { + if ($totalMercanciasGravadas != '') { $xmlString .= ' - ' . $totalMercGravadas . ''; + ' . $totalMercanciasGravadas . ''; } - if ($totalMercExentas != '') { + if ($totalMercanciasExentas != '') { $xmlString .= ' - ' . $totalMercExentas . ''; + ' . $totalMercanciasExentas . ''; } - if ($totalGravados != '') { + if ($totalGravado != '') { $xmlString .= ' - ' . $totalGravados . ''; + ' . $totalGravado . ''; } if ($totalExento != '') { @@ -5089,7 +5227,7 @@ function genXMLFee() } $xmlString .= ' - ' . $totalVentas . ''; + ' . $totalVenta . ''; if ($totalDescuentos != '') { $xmlString .= ' @@ -5330,7 +5468,6 @@ function genXMLFee() "xml" => base64_encode($xmlString), "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); - } @@ -5342,5 +5479,3 @@ function test() { return "Esto es un test"; } - -?> diff --git a/api/contrib/signXML/Firmadohaciendacr.php b/api/contrib/signXML/Firmadohaciendacr.php index 98a7c04d..a420e2f3 100644 --- a/api/contrib/signXML/Firmadohaciendacr.php +++ b/api/contrib/signXML/Firmadohaciendacr.php @@ -125,8 +125,11 @@ public function firmar($certificadop12, $clavecertificado, $xmlsinfirma, $tipodo public function insertaFirma($xml) { - if (is_null($this->publicKey) || is_null($this->privateKey)) { - return $xml; + if (is_null($this->publicKey) || is_null($this->privateKey) || is_null($xml)) { + tools_reply([ + "Status" => 400, + "text" => "Al firmar, el documento o las claves no pueden estar vacías", + ]); } // Canoniza todo el documento para el digest @@ -167,6 +170,33 @@ public function insertaFirma($xml) } $certIssuer = implode(', ', array_reverse($certIssuer)); + + $startPos = strpos($xml, "", 0); + $clave = substr($xml, $startPos+7, 50); + + $issuerCedula = $certData['subject']['serialNumber']; + $issuerCedula = preg_replace('/^CPF/', '', $issuerCedula); + $issuerCedula = str_replace('-', '', $issuerCedula); + $issuerCedula = ltrim($issuerCedula, '0'); + if(strpos($clave, $issuerCedula) === false){ + tools_reply([ + "Status" => 400, + "text" => "El número de cédula del certificado ($issuerCedula) no coincide con el indicado en la CLAVE en el XML ($clave)", + ]); + } + + $startPos = strpos($xml, "", 0); + $startPos = strpos($xml, "", $startPos); + $startPos = strpos($xml, "", $startPos); + $endPos = strpos($xml, "", $startPos); + $emisorNumero = substr($xml, $startPos+8, $endPos -($startPos+8)); + + if($emisorNumero !== $issuerCedula){ + tools_reply([ + "Status" => 400, + "text" => "El número de cédula del certificado ($issuerCedula) no coincide con el indicado en el EMISOR en el XML ($emisorNumero)", + ]); + } if (strpos($certData['serialNumber'], "0x") === false) { // https://bugs.php.net/bug.php?id=77411 @@ -175,7 +205,7 @@ public function insertaFirma($xml) $serialNumber = stringHex2StringDec($certData['serialNumber']); } - $prop = '' . + $prop = '' . ''. '' . $signTime1 . '' . ''. diff --git a/api/core/tools.php b/api/core/tools.php index eede23ff..9331d95f 100644 --- a/api/core/tools.php +++ b/api/core/tools.php @@ -49,12 +49,15 @@ function tools_reply($response, $killMe = false) } if (is_array($response) && isset($response['Status'])) { - // propagar l codigo de estado HTTP cuando se recibe error de Hacienda + // propagar el codigo de estado HTTP cuando se recibe en el error if (is_numeric($response['Status'])) { http_response_code($response['Status']); $killMe = $response['Status'] >= 299; - unset($response['text'][0]); - + if(is_array($response['text']) && count($response['text']) > 1) { + // cuando la respuesta es un array de Hacienda, el primer elemento es el código de estado + // y el resto es el mensaje + unset($response['text'][0]); + } } else { switch ($response['Status']) { case 'error': diff --git a/api/tools/ValidadorXML.php b/api/tools/ValidadorXML.php index 7e90726c..512bc1f4 100644 --- a/api/tools/ValidadorXML.php +++ b/api/tools/ValidadorXML.php @@ -1,31 +1,25 @@ code; - + $r = []; switch ($error->level) { case LIBXML_ERR_WARNING: - $return["tipo"] = "warning"; + $r["tipo"] = "warning"; break; case LIBXML_ERR_ERROR: - $return["tipo"] = "error"; + $r["tipo"] = "error"; break; case LIBXML_ERR_FATAL: - $return["tipo"] = "fatal_error"; + $r["tipo"] = "fatal_error"; break; } - $return["mensaje"] = $error->message; - //$return["archivo"] = $error->file; - //$return["linea"] = $error->line; + $r["mensaje"] = $error->message; - return $return; + return $r; } private static function libxml_display_errors() @@ -83,11 +77,19 @@ public static function validateXml($contenido_xml, $consecutivo, $version = "4.4 $xml->loadXML($contenido_xml); if (!$xml->schemaValidate($schemas[$tipo_doc])) { - return (object) [ - "status" => "ok", + $errors = ValidadorXML::libxml_display_errors(); + $r = [ + "status" => "error", "schema" => $schemas[$tipo_doc], - "message" => ValidadorXML::libxml_display_errors() + "message" => $errors, + "xml" => $contenido_xml ]; + if(count($errors) === 0) { + $r["status"] = "ok"; + $r["message"] = "El XML es válido"; + unset($r["xml"]); + } + return (object) $r; } } } diff --git a/www/xsd/FacturaElectronica_V4.4.xsd b/www/xsd/FacturaElectronica_V4.4.xsd index d042b8ed..ddb53060 100644 --- a/www/xsd/FacturaElectronica_V4.4.xsd +++ b/www/xsd/FacturaElectronica_V4.4.xsd @@ -1 +1,3250 @@ - Elemento Raiz de la Facturacion Electrónica Corresponde a la clave del comprobante. Es un campo fijo de cincuenta posiciones y se tiene que utilizar para la consulta del código QR Se debe indicar el número de cedula de identificación del proveedor de sistemas que esté utilizando para la emisión de comprobantes electrónicos Se debe de indicar el código de la actividad económica inscrita a la cual corresponde el comprobante que se está generando Se debe de indicar el código de la actividad económica inscrita del receptor a la cual corresponden los bienes o servicios que se le están facturando al receptor en caso de ser requerido para un crédito o un gasto deducible. Numeración consecutiva del comprobante Emisor del documento Receptor del documento Condiciones de la venta: 01 Contado, 02 Crédito, 03 Consignación, 04 Apartado, 05 Arrendamiento con opción de compra, 06 Arrendamiento en función financiera, 07 Cobro a favor de un tercero, 08 servicxios prestados al estado a credito, 09 pago del servicio prestado al estado,10 venta a crédito hasta 90 dias,11 pago de venta a crédito en IVA hasta 90 dias, 99 Otros Contado Crédito Consignación Apartado Arrendamiento con opción de compra Arrendamiento en función financiera Cobro a favor de un tercero Servicios prestados al Estado a crédito Venta a crédito en IVA hasta 90 días (Artículo 27, LIVA) Venta Mercancía No Nacionalizada Venta Bienes Usados No Contribuyente Arrendamiento Operativo Arrendamiento Financiero Otros Será obligatorio en caso de utilizar el código 99 de "Otros" de la nota 5. Se debe describir puntualmente la condición de la venta utilizada. Plazo del crédito, es obligatorio cuando la venta del producto o prestación del servicio sea a crédito Detalle del Servicio, Mercancía u otro Cada línea del detalle de la mercancia o servicio prestado. Número de línea del detalle Código de Producto/servicio Cantidad Unidad de medida Este campo se utilizará para identificar el tipo de transacción que se realizará. Unidad de medida comercial Detalle de la mercancia transferida o servicio prestado Número de VIN o Serie del medio de transporte Se refiere al respectivo número de registro del Ministerio de Salud Código de la presentación del medicamento. Tipo complejo que representa cada línea del detalle de los componentes de un surtido, paquete o combinación de productos. Se debe utilizar exclusivamente cuando en la línea de detalle se está facturando un paquete, surtido o combo, entendido como la combinación de más de dos productos con diferentes códigos de producto/servicio. Tipo complejo que representa cada línea del detalle del surtido Código de Producto /Servicio componente de Surtido Código del producto del vendedor Código del producto del comprador código del producto asignado por la industria código de uso interno Otros Es un número decimal compuesto por 13 enteros y 3 decimales. Nodo utilizado para indicar una unidad de medida que nace del propio giro comercial del establecimiento, no es una cantidad estandarizada de una determinada magnitud física, definida y adoptada por convención o por ley ejemplo: "1 Tarima" Detalle de la mercancía transferida o servicio prestado incluido en el surtido Se obtiene de la multiplicación del campo "Cantidad componente de surtido" por el campo "Precio unitario componente de surtido". Se puede incluir un máximo de 5 repeticiones de descuentos, cada descuento adicional se calcula sobre la base menos el descuento anterior. Validación: Se deberá incluir un valor igual o menor al del "Monto total componente surtido" Este campo será de condición obligatoria, cuando se incluya información en el campo "Monto de descuentos concedidos al componente de surtido Este campo será de condición obligatoria, cuando se utilice el código 99 de la Nota 20 Validación: En caso de utilizarse el código 99, se verificará que este campo se encuentre en el comprobante, caso contrario se rechazará. Además, deberá contener mínimo 3 caracteres y un máximo de 80 Se obtiene de la resta del campo "Monto total componente surtido" menos "Monto de descuentos concedidos al componente de surtido" En este campo se indicará si el Impuesto al Valor Agregado fue cobrado a nivel de fábrica, por lo que deberá ser utilizado únicamente por los obligados tributarios a realizar el pago de esta forma. Se convierte en obligatorio cuando el IVA se cobra o se cobró a nivel de fábrica. Al hacer uso del presente campo el producto se entenderá exento para el código 02, por lo cual no deberá llenar el subnodo de impuestos para el cálculo del IVA. ▪Para el código 01 el emisor puede separar los impuestos que está cobrando en la fábrica. Venta de bienes con IVA según el sistema especial de determinación de IVA a nivel de fábrica (Se utiliza cuando se está cobrando el IVA a nivel de fábrica Ventas exentas según el sistema especial de determinación de IVA a nivel de fábrica, mayorista y aduanas (se utiliza cuando el producto se encuentra exento ya que el bien soporto el cobro de impuestos a nivel de fábrica). Este campo será de condición obligatoria, cuando el producto este gravado con algún impuesto. Se obtiene de la suma entre el campo "Subtotal componente del surtido", más el impuesto selectivo de consumo (02), el Impuesto específico de Bebidas Alcohólicas (04) y el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05), cuando corresponda. Este campo se podrá editar cuando se seleccione en el campo "IVA cobrado a nivel de fábrica" el Código 01 o en el campo de "Código del impuesto" el código 07. Validación: En caso de utilizarse el código 01, en el campo de IVA cobrado a nivel de fábrica, se verificará que este campo se encuentre en el comprobante, caso contrario se rechazará. Además, se deberá incluir un valor mayor a "cero". Ver nota 8. Es un campo fijo de dos posiciones. Al utilizar el código de Naturaleza del Descuento 01 correspondiente a "Regalías" o 03 de "Bonificaciones" y el código de impuesto 01, se debe utilizar para el cálculo del impuesto el campo denominado "Monto total componente de surtido" y la "Tarifa del Impuesto al Valor Agregado para componente de surtido" Validación: Se verificará el cumplimiento de la nota 8 Será obligatorio en caso de utilizar el código 99 de "Otros" de la nota 8. Se debe describir puntualmente el impuesto utilizado. Validación: Deberá contener mínimo 5 caracteres y un máximo de 100 Se convierte en obligatorio cuando se usa el código 01 de impuestos de surtido Validación: Se verificará el cumplimiento de nota 8.1. cuando se utilice el código 01 de campo código del impuesto. Este campo es de condición obligatoria, cuando el componente este gravado con alguna tarifa de impuesto, según corresponda. Debe de expresarse el porcentaje como número entero (Ejemplo: la tarifa del 13% se debe de reflejar como 13, la tarifa del 1% como 1, o bien la tarifa del 0.5% como 0.5) Datos para Impuestos Específicos para componente de surtido Cantidad de la unidad de medida a utilizar para componente de surtido. Este campo es de condición obligatoria, cuando se utilicen los códigos de impuesto 04, 05 y 06 de la nota 8 Porcentaje en componente de surtido. Este campo es de condición obligatoria, cuando se utilice el código de impuesto 04 de la nota 8. Debe de expresarse el porcentaje como número entero (Ejemplo: la tarifa del 13% se debe de reflejar como 13, la tarifa del 1% como 1, o bien la tarifa del 0.5% como 0.5) Este campo es de condición obligatoria, cuando se utilice el código de impuesto 04 de la nota 8 Este campo se obtiene de multiplicar la "Cantidad de la unidad de medida a utilizar" por el "Porcentaje" Volumen por Unidad de Consumo componente de surtido. Este campo es de condición obligatoria, cuando se utilice el código de impuesto 05 de la nota 8 Este campo es de condición obligatoria, cuando se utilicen los códigos de impuesto 04, 05 y 06 de la nota 8 Este campo será de condición obligatoria, cuando el componente este gravado con algún impuesto. Es un número decimal compuesto por 13 enteros y 5 decimales Precio Unitario Se obtiene de multiplicar el campo cantidad por el campo precio unitario Se obtiene de la resta del campo monto total menos monto de descuento concedido En este campo se indicará si el Impuesto al Valor Agregado fue cobrado a nivel de fábrica, por lo que deberá ser utilizado únicamente por los obligados tributarios a realizar el pago de esta forma. Se convierte en obligatorio cuando el IVA se cobra o se cobró a nivel de fábrica. Al hacer uso del presente campo el producto se entenderá exento para el código 02, por lo cual no deberá llenar el subnodo de impuestos para el cálculo del IVA. Para el código 01 el emisor puede separar los impuestos que está cobrando en la fábrica. Venta de bienes con IVA según el sistema especial de determinación de IVA a nivel de fábrica (Se utiliza cuando se está cobrando el IVA a nivel de fábrica Ventas exentas según el sistema especial de determinación de IVA a nivel de fábrica, mayorista y aduanas (se utiliza cuando el producto se encuentra exento ya que el bien soporto el cobro de impuestos a nivel de fábrica). Este campo será de condición obligatoria, cuando el producto/ servicio este gravado con algún impuesto. Se obtiene de la suma entre el campo "Subtotal", más el impuesto selectivo de consumo (02), el Impuesto específico de Bebidas Alcohólicas (04), el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05) y el impuesto al cemento (12), cuando corresponda. Este campo se podrá editar cuando se seleccione en el campo "IVA cobrado a nivel de fábrica" el Código 01 o en el campo de "Código del impuesto" el código 07. Cuando el producto o servicio este gravado con algún impuesto se debe indicar cada uno de ellos. Impuestos Asumidos por el Emisor o cobrado a Nivel de Fábrica Este monto se obtiene al restar el campo “Monto del Impuesto” menos “Monto del Impuesto Exonerado” o el campo “Impuestos Asumidos por el Emisor o cobrado a Nivel de Fábrica” cuando corresponda Se calcula de la siguiente manera: se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. Información sobre otros cargos Total de los servicios gravados con IVA Total de los servicios exentos de IVA Total servicios exonerados del IVA Este campo será de condición obligatoria, cuando se seleccionen códigos CAByS que correspondan a un servicio y el servicio sea No Sujeto de IVA Total mercancias gravadas con IVA Total mercancias exentas de IVA Total mercancías exoneradas del IVA Este campo será de condición obligatoria, cuando se seleccionen códigos CAByS que correspondan a una mercancía y la mercancía sea No Sujeta de IVA Total gravado. se obtiene de la suma del total servicios gravados con IV + total mercancias gravadas con IV Total Exento, se obtiene de la suma de los campos total servicios exentos IV mas total mercancias exentas IV Se obtiene de la suma de los campos "total servicios exonerados de IVA" mas "total de mercancías exoneradas del IVA". Se obtiene de la suma de los campos "Total servicios No Sujetos de IVA" mas "Total mercancías No Sujetas de IVA". Se obtiene de la sumatoria de los campos “total gravado”, “total exento”, “Total Exonerado” y “Total No Sujeto Se obtiene de la suma de todos los campo de monto de descuento concedido Se obtiene de la resta de los campos total venta menos total descuento Tipo complejo que contiene los montos desglosados por impuesto cobrado en el comprobante electrónico. Indicará los códigos de impuesto registrados en las líneas de detalle. Se obtiene de la sumatoria del monto por código de impuesto cobrado en el comprobante electrónico Se obtiene de la suma de todos campos monto del impuesto Este campo es de condición obligatoria, cuando existen producto/servicio gravados con algún impuesto en las líneas de detalle que sean asumidos por el emisor IVA Devuelto Total Otros Cargos Corresponde al medio de pago empleado: 01 - Efectivo, 02 - Tarjeta, 03 - Cheque, 04 - Transferencia - depósito bancario, 05 - Recaudado por terceros, 06 - SINPE MOVIL, 07 - Plataforma Digital, 99 - Otros Efectivo Tarjeta Cheque Transferencia - depósito bancario Recaudado por terceros SINPE MOVIL Plataforma Digital Otros Será obligatorio en caso de utilizar el código 99 de "Otros" de la nota 6. Se debe describir puntualmente el medio de pago utilizado Se deberá detallar el monto correspondiente al tipo de pago seleccionado. Se volverá obligatorio cuando se utilice más de un medio de pago. Se obtiene de la suma de los campos "total venta neta", "monto total del impuesto" y "total otros cargos" menos "total IVA devuelto", en caso de contar con dichos campos. Tipo de documento de referencia Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 10. Se debe describir puntualmente el tipo de documento utilizado Clave numérica del comprobante electrónico o consecutivo del documento de referencia Fecha de emisión del documento de referencia Código de referencia. 01 Anula documento de referencia, 02 Corrige texto de documento de referencia, 04 Referencia a otro documento, 05 Sustituye comprobante provisional por contigencia, 99 Otros Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 9. Se debe describir puntualmente el código de referencia utilizado Razón de referencia Elemento opcional que se puede utilizar para almacenar texto. Código opcional para facilitar la identificación del elemento. Elemento opcional que se puede utilizar para almacenar contenido estructurado. Código opcional para facilitar la identificación del elemento. Nombre o razon social Campo condicional. Se convierte en carácter obligatorio cuando se estén facturando códigosCAByS de bebidas alcohólicas según la Ley 8707. Contiene los datos del número de registro de bebidas alcohólicas, suministrado por la Dirección General de Aduanas En caso de que se cuente con nombre comercial debe indicarse Debe cumplir con la siguiente estructura: \s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s* Nombre o razon social En caso de que se cuente con nombre comercial debe indicarse Campo para incluir la direccion del extranjero, en caso de requerirse. Este campo será de condición obligatoria, cuando el cliente lo requiera. Debe cumplir con la siguiente estructura: \s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s* Tipo de identificación: 01 Cédula Física, 02 Cédula Jurídica, 03 DIMEX, 04 NITE, 05 Extranjero No Domiciliado, 06 No Contribuyente Cedula Fisica Cedula Juridica DIMEX NITE Extranjero No Domiciliado No Contribuyente Número de identificación, el contribuyente debe estar inscrito ante la Administración Tributaria Código del país Número de teléfono Tipo de documento de exoneración o autorización. Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 10.1. Se debe describir puntualmente el tipo de documento o autorización utilizado Número de documento de exoneración o autorización Número de artículo que establece la exoneración o autorización Número de inciso que establece la exoneración o autorización Nombre de la institución o dependencia que emitió la exoneración Ministerio de Hacienda Ministerio de Relaciones Exteriores y Culto Ministerio de Agricultura y Ganadería Ministerio de Economía, Industria y Comercio Cruz Roja Costarricense Benemérito Cuerpo de Bomberos de Costa Rica Asociación Obras del Espíritu Santo Federación Cruzada Nacional de protección al Anciano (Fecrunapa) Escuela de Agricultura de la Región Húmeda (EARTH) Instituto Centroamericano de Administración de Empresas (INCAE) Junta de Protección Social (JPS) Autoridad Reguladora de los Servicios Públicos (Aresep) Otros Detalle Nombre de institución o dependencia que emitió la exoneración OTRO Fecha y hora de la emisión del documento de exoneración o autorización. Tarifa exonerada Monto del impuesto exonerado Código del impuesto: 01 Impuesto al valor agregado, 02 Impuesto Selectivo de Consumo, 03 Impuesto único a los combustivos, 04 Impuesto específico de bebidas alcohólicas, 05 Impuesto específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador, 06 Impuesto a los productos de tabaco, 07 IVA (cálculo especial), 08 IVA Regimen de Bienes Usados (Factor), 12 Impuesto Especifico al cemento, 99 Otros Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 8. Se debe describir puntualmente el impuesto utilizado En el caso que se utilice el nodo “Detalle de productos del surtido, paquetes o combos”, no se deberá utilizar este campo, ya que el impuesto se calcula como la suma de los montos de impuestos individuales de las líneas de detalle de los componentes del surtido que se deben incluir en estos casos. La eventual validación de la consistencia de los impuestos calculados y aplicación de tarifas se hará sobre las líneas individuales de detalle. En el caso que se utilice el nodo “Detalle de productos del surtido, paquetes o combos”, no se deberá utilizar este campo, ya que el impuesto se calcula como la suma de los montos de impuestos individuales de las líneas de detalle de componentes del surtido que se deben incluir en estos casos. La eventual validación de la consistencia de los impuestos calculados y aplicación de tarifas se hará sobre las líneas individuales de detalle. Este campo es de condición obligatoria, cuando el producto/servicio posea un factor para su cálculo. Cuando en el código de impuesto se defina IVA Bienes Usados se deberá utilizar este campo con el factor establecido por el Ministerio de Hacienda Tipo complejo con el detalle para calcular impuestos específicos no tarifarios. Este campo es de condición obligatoria, cuando se utilicen los códigos de impuesto 03, 04, 05, 06 de la nota 8 y agrupará los campos requeridos para el cálculo de estos impuestos En el caso que se utilice el nodo “Detalle de productos del surtido, paquetes o combos”, no se deberá utilizar este campo, para los códigos de impuesto 04, 05, 06 de la nota 8, ya que el impuesto se calcula como la suma de los montos de impuestos individuales de las líneas de detalle de componentes del surtido que se deben incluir en estos casos Este campo es de condición obligatoria, cuando se utilice el código de impuesto 04 de la nota 8 Este campo es de condición obligatoria, cuando se utilice el código de impuesto 04 de la nota 8 Este campo se obtiene de multiplicar la “Cantidad de la unidad de medida a utilizar” por el “Porcentaje” Este campo es de condición obligatoria, cuando se utilice el código de impuesto 05 de la nota 8 Este campo es de condición obligatoria, cuando se utilicen los códigos de impuesto 03, 04, 05 y 06 de la nota 8 Monto del impuesto Será obligatorio para las líneas de detalle que utilicen uno de los códigos de producto/servicio de "surtidos" que estén habilitados en el CAByS. En el caso de la inclusión de paquetes, surtidos o combos, entendidos como la combinación de más de dos productos con diferentes códigos de producto/servicio, se debe seleccionar el código 03 "Código del producto asignado por la industria" de la nota 12 e incluir en el campo "código" el respectivo código "SKU", GTIN o equivalentes, con el que el paquete este identificado en la industria. Estos códigos deben ser verificables en los catálogos disponibles en la industria. Código del producto del vendedor Código del producto del comprador código del producto asignado por la industria código de uso interno Otros Será obligatorio para las líneas de detalle que utilicen uno de los códigos de producto/servicio de "surtidos" que estén habilitados en el CAByS. Monto de descuento concedido. Este campo será de condición obligatoria, cuando se indique un descuento, en el campo "Código del descuento" Este campo será de condición obligatoria, cuando se incluya información en el campo "monto de descuentos concedidos" Será obligatorio en caso de utilizar el código 99 de "Otros" de la nota 20. Se debe describir puntualmente el descuento utilizado Naturaleza del descuento, que es obligatorio si existe descuento Se verificará el cumplimiento de la nota 16. Además, cuando se seleccione el código 04, 08, 09 y 10 de la nota 16 en “Tipo de documento otros cargos” y no se cuente con una línea de servicio o producto, no es obligatorio usar el nodo “Detalle de la mercancía o servicio prestado”. Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 16. Se debe describir puntualmente el tipo de documento utilizado Nombre o razón social del receptor Detalle de otros cargos En el caso que el cargo posea un porcentaje o monto para su cálculo se debe de indicar el mismo Monto del cargo Código de la moneda Tipo de cambio Tipo de dato decimal para representar los valores de dinero. Tipo de dato String que solo permite el uso de números con un largo de 50. Tipo de dato String que solo permite el uso de números con un largo de 20 Unidades de Medida basadas en el estándar RTC 443:2010 uno (índice de refracción) minuto segundo grado Celsius 1 por metro Ampere ampere por metro ampere por metro cuadrado Activo Virtual Alquiler de uso habitacional Alquiler de uso comercial bel Becquerel coulomb coulomb por kilogramo coulomb por metro cuadrado coulomb por metro cúbico Cajuela de café Candela candela por metro cuadrado Comisiones centímetro cuartillos de café día electronvolt farad farad por metro fanega de café Gramo Galón gray gray por segundo hora henry henry por metro hertz Intereses Joule joule por kilogramo kelvin joule por mol kelvin joule por kelvin joule por kilogramo joule por metro cúbico joule por mol Kelvin katal katal por metro cúbico Kilogramo kilogramo por metro cúbico Kilometro kilovatios Kilovatios por hora litro lumen pulgada lux Metro metro por segundo metro por segundo cuadrado metro cuadrado metro cúbico minuto mililitro Milímetro Mol mol por metro cúbico newton newton por metro newton metro neper grado Otro tipo de servicio Se debe indicar la descripción de la medida a utilizar Onzas pascal pascal segundo Quintal radián radián por segundo radián por segundo cuadrado Segundo siemens Servicios Profesionales Servicios personales estereorradián Servicios técnicos sievert tesla tonelada unidad de masa atómica unificada unidad astronómica Unidad volt volt por metro Watt watt por metro kevin watt por metro cuadrado estereorradián watt por metro cuadrado watt por estereorradián weber ohm Tipos de transacción para el caso de transacciones con tratamientos tributarios específicos Venta Normal de Bienes y Servicios (Transacción General) Mercancía de Autoconsumo exento Mercancía de Autoconsumo gravado Servicio de Autoconsumo exento Servicio de Autoconsumo gravado Cuota de afiliación Cuota de afiliación Exenta Bienes de Capital para el emisor Bienes de Capital para el receptor Bienes de Capital para el emisor y el receptor Bienes de capital de autoconsumo exento para el emisor Bienes de capital sin contraprestación a terceros exento para el emisor Sin contraprestación a terceros Los tipos de descuentos a utilizar en el campo "Código del Descuento" del Descuento Descuento por Regalía Descuento por Regalía IVA Cobrado al Cliente Descuento por Bonificación Descuento por volumen Descuento por Temporada (estacional) Descuento promocional Descuento Comercial Descuento por frecuencia Descuento sostenido Otros descuentos Código del impuesto: 01 Impuesto al valor agregado, 02 Impuesto Selectivo de Consumo, 03 Impuesto único a los combustivos, 04 Impuesto específico de bebidas alcohólicas, 05 Impuesto específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador, 06 Impuesto a los productos de tabaco, 07 IVA (cálculo especial), 08 IVA Regimen de Bienes Usados (Factor), 12 Impuesto Especifico al cemento, 99 Otros Impuesto al Valor Agregado Impuesto Selectivo de Consumo Impuesto unico a los combustivos Impuesto especifico de bebidas alcohólicas impuesto especifico sobre las bebidas envasadas sin contenido alcoholico y jabones de tocador impuesto a los productos de tabaco IVA (cálculo especial) IVA Régimen de Bienes Usados (Factor) Impuesto Especifico al Cemento Otros Cuando se trata del IVA las tarifas y códigos a utilizar son las siguientes Tarifa 0% (Artículo 32, num 1, RLIVA) Tarifa reducida 1% Tarifa reducida 2% Tarifa reducida 4% Transitorio 0% Transitorio 4% Tarifa transitoria 8% Tarifa general 13% Tarifa reducida 0.5% Tarifa Exenta Tarifa 0% sin derecho a crédito Tipo de documento de exoneración o de autorización. Compras autorizadas por la Dirección General de Tributación Ventas exentas a diplomáticos Autorizado por Ley Especial Exenciones Dirección General de Hacienda Autorización Local Genérica Exenciones Dirección General de Hacienda Transitorio V (servicios de ingeniería, arquitectura, topografía obra civil) Servicios turísticos inscritos ante el Instituto Costarricense de Turismo (ICT) Transitorio XVII (Recolección, Clasificación, almacenamiento de Reciclaje y reutilizable) Exoneración a Zona Franca Exoneración de servicios complementarios para la exportación articulo 11 RLIVA Órgano de las corporaciones municipales Exenciones Dirección General de Hacienda Autorización de Impuesto Local Concreta Otros Tipo de documento otros cargos Contribución parafiscal Timbre de la Cruz Roja Timbre de Benemérito Cuerpo de Bomberos de Costa Rica Cobro de un tercero Costos de Exportación Impuesto de servicio 10% Timbre de Colegios Profesionales Depósitos de Garantía Multas o Penalizaciones Intereses Moratorios Otros Cargos Factura electrónica Nota de debido electrónica nota de crédito electrónica Tiquete electrónico Nota de despacho Contrato Procedimiento Comprobante emitido en contigencia Devolución mercadería Comprobante electrónico rechazado por el Ministerio de Hacienda Sustituye factura rechazada por el Receptor del comprobante Sustituye Factura de exportación Facturación mes vencido Otros Comprobante aportado por contribuyente de Régimen Especial. Sustituye una Factura electrónica de Compra Comprobante de Proveedor No Domiciliado Nota de Crédito a Factura Electrónica de Compra Nota de Débito a Factura Electrónica de Compra Anula documento de referencia Corrige texto de ocumento de referencia Referencia a otro documento Sustituye comprobante provisional por contigencia Devolución de mercancía Sustituye comprobante electrónico Factura Endosada Nota de crédito financiera Nota de débito financiera Proveedor No Domiciliado Crédito por exoneración posterior a la facturación Otros \ No newline at end of file + + + + + + Elemento Raiz de la Facturacion Electrónica + + + + + + + Corresponde a la clave del comprobante. + Es un campo fijo de cincuenta posiciones y se tiene que utilizar parala consulta del código QR + + + + + + + Se debe indicar el número de cedula de identificación del proveedor de sistemas + que esté utilizando para la emisión de comprobantes electrónicos + + + + + + + + + + + + Se debe de indicar el código de la actividad económica inscrita a la cual corresponde + el comprobante que se está generando + + + + + + + + + + + + + Se debe de indicar el código de la actividad económica inscrita del receptor a la cual corresponden + los bienes o servicios que se le están facturando al receptor en caso de ser requerido para un crédito + o un gasto deducible. + + + + + + + + + + + + Numeración consecutiva del comprobante + + + + + + Emisor del documento + + + + + Receptor del documento + + + + + + Condiciones de la venta: + 01 Contado, + 02 Crédito, + 03 Consignación, + 04 Apartado, + 05 Arrendamiento con opción de compra, + 06 Arrendamiento en función financiera, + 07 Cobro a favor de un tercero, + 08 servicxios prestados al estado a credito, + 09 pago del servicio prestado al estado, + 10 venta a crédito hasta 90 dias, + 11 pago de venta a crédito en IVA hasta 90 dias, + 99 Otros + + + + + + + Contado + + + + + Crédito + + + + + Consignación + + + + + Apartado + + + + + Arrendamiento con opción de compra + + + + + Arrendamiento en función financiera + + + + + Cobro a favor de un tercero + + + + + Servicios prestados al Estado a crédito + + + + + Venta a crédito en IVA hasta 90 días (Artículo 27, LIVA) + + + + + Venta Mercancía No Nacionalizada + + + + + Venta Bienes Usados No Contribuyente + + + + + Arrendamiento Operativo + + + + + Arrendamiento Financiero + + + + + Otros + + + + + + + + + Será obligatorio en caso de utilizar el código 99 de "Otros" de la nota 5. + Se debe describir puntualmente la condición de la venta utilizada. + + + + + + + + + + + + + Plazo del crédito, es obligatorio cuando la venta del producto o prestación del servicio sea a crédito + + + + + + + + + + + + Detalle del Servicio, Mercancía u otro + + + + + + + Cada línea del detalle de la mercancia o servicio prestado. + + + + + + Número de línea del detalle + + + + + + + + + + + Código de Producto/servicio + + + + + + + + + + + + Cantidad + + + + + + + + + + + Unidad de medida + + + + + + Este campo se utilizará para identificar el tipo de transacción que se realizará. + + + + + + + + + + + Unidad de medida comercial + + + + + + + + + + Detalle de la mercancia transferida o servicio prestado + + + + + + + + + + + + Número de VIN o Serie + del medio de transporte + + + + + + + + + + + Se refiere al respectivo número de registro del Ministerio de Salud + + + + + + + + + + Código de la presentación del medicamento. + + + + + + + + + + Tipo complejo que representa cada línea del detalle de los componentes de un surtido, paquete o combinación de productos. Se debe utilizar exclusivamente cuando en la línea de detalle se está facturando un paquete, surtido o combo, entendido como la combinación de más de dos productos con diferentes códigos de producto/servicio. + + + + + + Tipo complejo que representa cada línea del detalle del surtido + + + + + + Código de Producto/Servicio componente de Surtido + + + + + + + + + + + + + + + + + + Código del producto del vendedor + + + + + Código del producto del comprador + + + + + código del producto asignado por la industria + + + + + código de uso interno + + + + + Otros + + + + + + + + + + + + + + + + + + + Es un número decimal compuesto por 13 enteros y 3 decimales. + + + + + + + + + + + + + + + + + + + Nodo utilizado para indicar una unidad de medida que nace del propio giro comercial del establecimiento, no es una cantidad estandarizada de una determinada magnitud física, definida y adoptada por convención o por ley ejemplo: "1 Tarima" + + + + + + + + + + Detalle de la mercancía transferida o servicio prestado incluido en el surtido + + + + + + + + + + + + + + + + + + Se obtiene de la multiplicación del campo "Cantidad componente de surtido" por el campo "Precio unitario componente de surtido". + + + + + + Se puede incluir un máximo de 5 repeticiones de + descuentos, cada descuento adicional se calcula sobre la base menos el descuento anterior. + + + + + + + + Validación: Se deberá incluir un valor igual o menor al del + "Monto total componente surtido" + + + + + + Este campo será de condición obligatoria, cuando se incluya información en el campo "Monto de descuentos concedidos al componente de surtido + + + + + + Este campo será de condición obligatoria, cuando se utilice el código 99 de la Nota 20 + Validación: En caso de utilizarse el código 99, se verificará que este campo se encuentre en el comprobante, caso contrario se rechazará. Además, deberá contener mínimo 3 caracteres y un máximo de 80 + + + + + + + + + + + + + + + Se obtiene de la resta del campo "Monto total componente surtido" menos "Monto de descuentos concedidos al componente de surtido" + + + + + + En este campo se indicará si el Impuesto al Valor Agregado fue cobrado a nivel de fábrica, por lo que deberá ser utilizado únicamente por los obligados tributarios a realizar el pago de esta forma. + Se convierte en obligatorio cuando el IVA se cobra o se cobró a nivel de fábrica. + Al hacer uso del presente campo el producto se entenderá exento para el código 02, por lo cual no deberá llenar el subnodo de impuestos para el cálculo del IVA. + ▪Para el código 01 el emisor puede separar los impuestos + que está cobrando en la fábrica. + + + + + + + + + Venta de bienes con IVA según el sistema especial de determinación de IVA a nivel de fábrica (Se utiliza cuando se está cobrando el IVA a nivel de fábrica + + + + + + Ventas exentas según el sistema especial de determinación de IVA a nivel de fábrica, mayorista y aduanas (se utiliza cuando el producto se encuentra exento ya que el bien soporto el cobro de impuestos a nivel de fábrica). + + + + + + + + + Este campo será de condición obligatoria, cuando el producto este gravado con algún impuesto. Se obtiene de la suma entre el campo "Subtotal componente del surtido", más el impuesto selectivo de consumo (02), el Impuesto específico de Bebidas Alcohólicas (04) y el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05), cuando corresponda. Este campo se podrá editar cuando se seleccione en el campo "IVA cobrado a nivel de fábrica" el Código 01 o en el campo de "Código del impuesto" el código 07. Validación: En caso de utilizarse el código 01, en el campo de IVA cobrado a nivel de fábrica, se verificará que este campo se encuentre en el comprobante, caso contrario se rechazará. Además, se deberá incluir un valor mayor a "cero". + + + + + + + + + Ver nota 8. Es un campo fijo de dos posiciones. + Al utilizar el código de Naturaleza del Descuento 01 correspondiente + a "Regalías" o 03 de "Bonificaciones" y el código de impuesto 01, + se debe utilizar para el cálculo del impuesto el campo denominado "Monto total componente de surtido" + y la "Tarifa del Impuesto al Valor Agregado para componente de surtido" + Validación: Se verificará el cumplimiento de la nota 8 + + + + + + + Será obligatorio en caso de utilizar el código 99 de "Otros" de la nota 8. Se debe describir puntualmente el impuesto utilizado. + Validación: Deberá contener mínimo 5 caracteres y un máximo de 100 + + + + + + + + + + + + + Se convierte en obligatorio cuando se usa el código 01 de impuestos de surtido + Validación: Se verificará el cumplimiento de nota 8.1. cuando se utilice el código 01 de campo código del impuesto. + + + + + + Este campo es de condición obligatoria, cuando el componente este gravado con alguna tarifa de impuesto, según corresponda. Debe de expresarse el porcentaje como número entero (Ejemplo: la tarifa del 13% se debe de reflejar como 13, la tarifa del 1% como 1, o bien la tarifa del 0.5% como 0.5) + + + + + + + + + + + Datos para Impuestos Específicos para componente de surtido + + + + + + Cantidad de la unidad de medida a utilizar para componente de surtido. Este campo es de condición obligatoria, cuando se utilicen los códigos de impuesto 04, 05 y 06 de la nota 8 + + + + + + + + + + + Porcentaje en componente de surtido. Este campo es de condición obligatoria, cuando se utilice el código de impuesto 04 de la nota 8. Debe de expresarse el porcentaje como número entero (Ejemplo: la tarifa del 13% se debe de reflejar como 13, la tarifa del 1% como 1, o bien la tarifa del 0.5% como 0.5) + + + + + + + + + + + + Este campo es de condición obligatoria, cuando se utilice el código de impuesto 04 de la nota 8 + Este campo se obtiene de multiplicar la "Cantidad de la unidad de medida a utilizar" por el "Porcentaje" + + + + + + + + + + + + Volumen por Unidad de Consumo componente de surtido. Este campo es de condición obligatoria, cuando se utilice el código de impuesto 05 de la nota 8 + + + + + + + + + + + Este campo es de condición obligatoria, cuando se utilicen los códigos de impuesto 04, 05 y 06 de la nota 8 + + + + + + + + Este campo será de condición obligatoria, cuando el componente este gravado con algún impuesto. Es un número decimal compuesto por 13 enteros y 5 decimales + + + + + + + + + + + + + + Precio Unitario + + + + + Se obtiene de multiplicar el campo cantidad por el campo precio unitario + + + + + + Se obtiene de la resta del campo monto total menos monto de descuento concedido + + + + + + En este campo se indicará si el Impuesto al Valor Agregado fue cobrado a nivel de fábrica, por lo que deberá ser utilizado únicamente por los obligados tributarios a realizar el pago de + esta forma. Se convierte en obligatorio cuando el IVA se cobra o se cobró a nivel de fábrica. Al hacer uso del presente campo el producto se entenderá exento para el código 02, por lo cual no deberá llenar el subnodo de impuestos para el cálculo del IVA. Para el código 01 el emisor puede separar los impuestos + que está cobrando en la fábrica. + + + + + + + + + Venta de bienes con IVA según el sistema especial de determinación de IVA a nivel de fábrica (Se utiliza cuando se está cobrando el IVA a nivel de fábrica + + + + + + Ventas exentas según el sistema especial de determinación de IVA a nivel de fábrica, mayorista y aduanas (se utiliza cuando el producto se encuentra exento ya que el bien soporto el cobro de impuestos a nivel de fábrica). + + + + + + + + + + Este campo será de condición obligatoria, cuando el producto/ servicio este gravado con algún impuesto. + Se obtiene de la suma entre el campo "Subtotal", más + el impuesto selectivo de consumo (02), + el Impuesto específico de Bebidas Alcohólicas (04), + el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05) y + el impuesto al cemento (12), cuando corresponda. + Este campo se podrá editar cuando se seleccione + en el campo "IVA cobrado a nivel de fábrica" el Código 01 + o en el campo de "Código del impuesto" el código 07. + + + + + + Cuando el producto o servicio este gravado con algún impuesto se debe indicar cada uno de ellos. + + + + + Impuestos Asumidos por el Emisor o cobrado a Nivel de Fábrica + + + + + + Este monto se obtiene al restar el campo “Monto del Impuesto” menos “Monto del Impuesto Exonerado” o el + campo “Impuestos Asumidos por el Emisor o cobrado a Nivel de Fábrica” cuando corresponda + + + + + + + Se calcula de la siguiente manera: + se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. + + + + + + + + + + + + Información sobre otros cargos + + + + + + + + + Total de los servicios gravados con IVA + + + + + Total de los servicios exentos de IVA + + + + + Total servicios exonerados del IVA + + + + + Este campo será de condición obligatoria, cuando se seleccionen códigos CAByS que correspondan a un servicio y el servicio sea No Sujeto de IVA + + + + + Total mercancias gravadas con IVA + + + + + Total mercancias exentas de IVA + + + + + Total mercancías exoneradas del IVA + + + + + Este campo será de condición obligatoria, cuando se seleccionen códigos CAByS que correspondan a una mercancía y la mercancía sea No Sujeta de IVA + + + + + Total gravado. se obtiene de la suma del total servicios gravados con IV + total mercancias gravadas con IV + + + + + Total Exento, se obtiene de la suma de los campos total servicios exentos IV mas total mercancias exentas IV + + + + + Se obtiene de la suma de los campos "total servicios exonerados de IVA" mas "total de mercancías exoneradas del IVA". + + + + + Se obtiene de la suma de los campos "Total servicios No Sujetos de IVA" mas "Total mercancías No Sujetas de IVA". + + + + + Se obtiene de la sumatoria de los campos “total gravado”, “total exento”, “Total Exonerado” y “Total No Sujeto + + + + + Se obtiene de la suma de todos los campo de monto de descuento concedido + + + + + Se obtiene de la resta de los campos total venta menos total descuento + + + + + Tipo complejo que contiene los montos desglosados por impuesto cobrado en el comprobante electrónico. + + + + + + Indicará los códigos de impuesto registrados en las líneas de detalle. + + + + + + Se obtiene de la sumatoria del monto por código de impuesto cobrado en el comprobante electrónico + + + + + + + + Se obtiene de la suma de todos campos monto del impuesto + + + + + Este campo es de condición obligatoria, cuando existen producto/servicio gravados con algún impuesto en las líneas de detalle que sean asumidos por el emisor + + + + + IVA Devuelto + + + + + Total Otros Cargos + + + + + + + + Corresponde al medio de pago empleado: 01 - Efectivo, 02 - Tarjeta, 03 - Cheque, 04 - Transferencia - depósito bancario, 05 - Recaudado por terceros, 06 - SINPE MOVIL, 07 - Plataforma Digital, 99 - Otros + + + + + + Efectivo + + + + + Tarjeta + + + + + Cheque + + + + + Transferencia - depósito bancario + + + + + Recaudado por terceros + + + + + SINPE MOVIL + + + + + Plataforma Digital + + + + + Otros + + + + + + + + Será obligatorio en caso de utilizar el código 99 de "Otros" de la nota 6. Se debe describir puntualmente el medio de pago utilizado + + + + + + + + + + + Se deberá detallar el monto correspondiente al tipo de pago seleccionado. Se volverá obligatorio cuando se utilice más de un medio de pago. + + + + + + + + Se obtiene de la suma de los campos "total venta neta", "monto total del impuesto" y "total otros cargos" menos "total IVA devuelto", en caso de contar con dichos campos. + + + + + + + + + + + Tipo de documento de referencia + + + + + Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 10. Se debe describir puntualmente el tipo de documento utilizado + + + + + + + + + + + Clave numérica del comprobante electrónico o consecutivo del documento de referencia + + + + + + + + + + Fecha de emisión del documento de referencia + + + + + Código de referencia. 01 Anula documento de referencia, 02 Corrige texto de documento de referencia, 04 Referencia a otro documento, 05 Sustituye comprobante provisional por contigencia, 99 Otros + + + + + Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 9. Se debe describir puntualmente el código de referencia utilizado + + + + + + + + + + + Razón de referencia + + + + + + + + + + + + + + + + Elemento opcional que se puede utilizar para almacenar texto. + + + + + + + Código opcional para facilitar la identificación del elemento. + + + + + + + + + Elemento opcional que se puede utilizar para almacenar contenido estructurado. + + + + + + + Código opcional para facilitar la identificación del elemento. + + + + + + + + + + + + + + + + + Nombre o razon social + + + + + + + + + + + + + Campo condicional. Se convierte en carácter obligatorio cuando se + estén facturando códigosCAByS de bebidas alcohólicas según la Ley + 8707. Contiene los datos del número de registro de bebidas + alcohólicas, suministrado por la Dirección General de Aduanas + + + + + + + + + + + En caso de que se cuente con nombre comercial debe indicarse + + + + + + + + + + + + + + Debe cumplir con la siguiente estructura: + \s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s* + + + + + + + + + + + + + + + Nombre o razon social + + + + + + + + + + + + En caso de que se cuente con nombre comercial debe indicarse + + + + + + + + + + + + Campo para incluir la direccion del extranjero, en caso de requerirse. + + + + + + + + + + + + + Este campo será de condición obligatoria, cuando el cliente lo requiera. Debe cumplir con la siguiente estructura: + \s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s* + + + + + + + + + + + + + + + Tipo de identificación: 01 Cédula Física, 02 Cédula Jurídica, 03 DIMEX, 04 NITE, 05 Extranjero No Domiciliado, 06 No Contribuyente + + + + + + Cedula Fisica + + + + + Cedula Juridica + + + + + DIMEX + + + + + NITE + + + + + Extranjero No Domiciliado + + + + + No Contribuyente + + + + + + + + Número de identificación, el contribuyente debe estar inscrito ante la Administración Tributaria + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Código del país + + + + + + + + + + Número de teléfono + + + + + + + + + + + + + + + Tipo de documento de exoneración o autorización. + + + + + Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 10.1. Se debe describir puntualmente el tipo de documento o autorización utilizado + + + + + + + + + + + Número de documento de exoneración o autorización + + + + + + + + + + + Número de artículo que establece la exoneración o autorización + + + + + + + + + + Número de inciso que establece la exoneración o autorización + + + + + + + + + + Nombre de la institución o dependencia que emitió la exoneración + + + + + + + Ministerio de Hacienda + + + + + Ministerio de Relaciones Exteriores y Culto + + + + + Ministerio de Agricultura y Ganadería + + + + + Ministerio de Economía, Industria y Comercio + + + + + Cruz Roja Costarricense + + + + + Benemérito Cuerpo de Bomberos de Costa Rica + + + + + Asociación Obras del Espíritu Santo + + + + + Federación Cruzada Nacional de protección al Anciano (Fecrunapa) + + + + + Escuela de Agricultura de la Región Húmeda (EARTH) + + + + + Instituto Centroamericano de Administración de Empresas (INCAE) + + + + + Junta de Protección Social (JPS) + + + + + Autoridad Reguladora de los Servicios Públicos (Aresep) + + + + + Otros + + + + + + + + Detalle Nombre de institución o dependencia que emitió la exoneración OTRO + + + + + + + + + + + Fecha y hora de la emisión del documento de exoneración o autorización. + + + + + Tarifa exonerada + + + + + + + + + + + Monto del impuesto exonerado + + + + + + + + + + Código del impuesto: + 01 Impuesto al valor agregado, + 02 Impuesto Selectivo de Consumo, + 03 Impuesto único a los combustivos, + 04 Impuesto específico de bebidas alcohólicas, + 05 Impuesto específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador, + 06 Impuesto a los productos de tabaco, + 07 IVA (cálculo especial), + 08 IVA Regimen de Bienes Usados (Factor), + 12 Impuesto Especifico al cemento, + 99 Otros + + + + + + + Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 8. + Se debe describir puntualmente el impuesto utilizado + + + + + + + + + + + + + En el caso que se utilice el nodo “Detalle de productos del surtido, paquetes o combos”, + no se deberá utilizar este campo, ya que el impuesto se calcula como la suma de los + montos de impuestos individuales de las líneas de detalle de los componentes del surtido + que se deben incluir en estos casos. La eventual validación de la consistencia de los + impuestos calculados y aplicación de tarifas se hará sobre las líneas individuales de detalle. + + + + + + + En el caso que se utilice el nodo “Detalle de productos del surtido, paquetes o combos”, + no se deberá utilizar este campo, ya que el impuesto se calcula como la suma de los montos + de impuestos individuales de las líneas de detalle de componentes del surtido que se + deben incluir en estos casos. La eventual validación de la consistencia de los + impuestos calculados y aplicación de tarifas se hará sobre las líneas individuales de detalle. + + + + + + + + + + + + + Este campo es de condición obligatoria, cuando el producto/servicio posea un factor para su cálculo. + Cuando en el código de impuesto se defina IVA Bienes Usados se deberá utilizar este campo con el factor establecido por el Ministerio de Hacienda + + + + + + + + + + + + + Tipo complejo con el detalle para calcular impuestos específicos no tarifarios. Este campo es de condición obligatoria, cuando se utilicen + los códigos de impuesto 03, 04, 05, 06 de la nota 8 y agrupará los campos requeridos para el cálculo de estos impuestos + + + + + + + + En el caso que se utilice el nodo “Detalle de productos del surtido, paquetes o combos”, no se deberá utilizar este + campo, para los códigos de impuesto 04, 05, 06 de la nota 8, ya que el impuesto se calcula como la suma de los montos de impuestos individuales de las líneas de detalle de + componentes del surtido que se deben incluir en estos casos + + + + + + + + + + + + Este campo es de condición obligatoria, cuando se utilice el código de impuesto 04 de la nota 8 + + + + + + + + + + + Este campo es de condición obligatoria, cuando se utilice el código de impuesto 04 de la nota 8 Este campo se obtiene de multiplicar la “Cantidad de la unidad de medida a utilizar” por el “Porcentaje” + + + + + + + + + + + Este campo es de condición obligatoria, cuando se utilice el código de impuesto 05 de la nota 8 + + + + + + + + + + + Este campo es de condición obligatoria, cuando se utilicen los códigos de impuesto 03, 04, 05 y 06 de la nota 8 + + + + + + + + Monto del impuesto + + + + + + + + + + + Será obligatorio para las líneas de detalle que utilicen uno de + los códigos de producto/servicio de "surtidos" que estén + habilitados en el CAByS. + En el caso de la inclusión de paquetes, surtidos o combos, + entendidos como la combinación de más de dos productos + con diferentes códigos de producto/servicio, se debe + seleccionar el código 03 "Código del producto asignado por la + industria" de la nota 12 e incluir en el campo "código" el + respectivo código "SKU", GTIN o equivalentes, con el que el + paquete este identificado en la industria. Estos códigos deben + ser verificables en los catálogos disponibles en la industria. + + + + + + + + + Código del producto del vendedor + + + + + Código del producto del comprador + + + + + código del producto asignado por la industria + + + + + código de uso interno + + + + + Otros + + + + + + + + + Será obligatorio para las líneas de detalle que utilicen uno de + los códigos de producto/servicio de "surtidos" que estén + habilitados en el CAByS. + + + + + + + + + + + + + + + + Monto de descuento concedido. Este campo será de condición obligatoria, cuando se indique + un descuento, en el campo "Código del descuento" + + + + + + + Este campo será de condición obligatoria, cuando se incluya + información en el campo "monto de descuentos concedidos" + + + + + + + Será obligatorio en caso de utilizar el código 99 de "Otros" + de la nota 20. Se debe describir puntualmente el descuento + utilizado + + + + + + + + + + + + Naturaleza del descuento, que es obligatorio si existe descuento + + + + + + + + + + + + + + + + Se verificará el cumplimiento de la nota 16. + Además, cuando se seleccione el código 04, 08, 09 y 10 de la nota 16 en “Tipo de documento otros cargos” y no se cuente con una línea de servicio o producto, no es obligatorio usar el nodo “Detalle de la mercancía o servicio prestado”. + + + + + + + + + + + + Será obligatorio en caso de utilizar el código 99 de “Otros” de la nota 16. Se debe describir puntualmente el tipo de documento utilizado + + + + + + + + + + + + Nombre o razón social del receptor + + + + + + + + + + + Detalle de otros cargos + + + + + + + + + + En el caso que el cargo posea un porcentaje o monto para su cálculo se debe de indicar el mismo + + + + + + + + + + + + Monto del cargo + + + + + + + + + Código de la moneda + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tipo de cambio + + + + + + + Tipo de dato decimal para representar los valores de dinero. + + + + + + + + + + + Tipo de dato String que solo permite el uso de números con un largo de 50. + + + + + + + + Tipo de dato String que solo permite el uso de números con un largo de 20 + + + + + + + + Unidades de Medida basadas en el estándar RTC 443:2010 + + + + + uno (índice de refracción) + + + + + minuto + + + + + segundo + + + + + grado Celsius + + + + + 1 por metro + + + + + Ampere + + + + + ampere por metro + + + + + ampere por metro cuadrado + + + + + Activo Virtual + + + + + Alquiler de uso habitacional + + + + + Alquiler de uso comercial + + + + + bel + + + + + Becquerel + + + + + coulomb + + + + + coulomb por kilogramo + + + + + coulomb por metro cuadrado + + + + + coulomb por metro cúbico + + + + + Cajuela de café + + + + + Candela + + + + + candela por metro cuadrado + + + + + Comisiones + + + + + centímetro + + + + + cuartillos de café + + + + + día + + + + + electronvolt + + + + + farad + + + + + farad por metro + + + + + fanega de café + + + + + Gramo + + + + + Galón + + + + + gray + + + + + gray por segundo + + + + + hora + + + + + henry + + + + + henry por metro + + + + + hertz + + + + + Intereses + + + + + Joule + + + + + joule por kilogramo kelvin + + + + + joule por mol kelvin + + + + + joule por kelvin + + + + + joule por kilogramo + + + + + joule por metro cúbico + + + + + joule por mol + + + + + Kelvin + + + + + katal + + + + + katal por metro cúbico + + + + + Kilogramo + + + + + kilogramo por metro cúbico + + + + + Kilometro + + + + + kilovatios + + + + + Kilovatios por hora + + + + + litro + + + + + lumen + + + + + pulgada + + + + + lux + + + + + Metro + + + + + metro por segundo + + + + + metro por segundo cuadrado + + + + + metro cuadrado + + + + + metro cúbico + + + + + minuto + + + + + mililitro + + + + + Milímetro + + + + + Mol + + + + + mol por metro cúbico + + + + + newton + + + + + newton por metro + + + + + newton metro + + + + + neper + + + + + grado + + + + + Otro tipo de servicio + + + + + Se debe indicar la descripción de la medida a utilizar + + + + + Onzas + + + + + pascal + + + + + pascal segundo + + + + + Quintal + + + + + radián + + + + + radián por segundo + + + + + radián por segundo cuadrado + + + + + Segundo + + + + + siemens + + + + + Servicios Profesionales + + + + + Servicios personales + + + + + estereorradián + + + + + Servicios técnicos + + + + + sievert + + + + + tesla + + + + + tonelada + + + + + unidad de masa atómica unificada + + + + + unidad astronómica + + + + + Unidad + + + + + volt + + + + + volt por metro + + + + + Watt + + + + + watt por metro kevin + + + + + watt por metro cuadrado estereorradián + + + + + watt por metro cuadrado + + + + + watt por estereorradián + + + + + weber + + + + + ohm + + + + + + + Tipos de transacción para el caso de transacciones con tratamientos tributarios específicos + + + + + Venta Normal de Bienes y Servicios (Transacción General) + + + + + Mercancía de Autoconsumo exento + + + + + Mercancía de Autoconsumo gravado + + + + + Servicio de Autoconsumo exento + + + + + Servicio de Autoconsumo gravado + + + + + Cuota de afiliación + + + + + Cuota de afiliación Exenta + + + + + Bienes de Capital para el emisor + + + + + Bienes de Capital para el receptor + + + + + Bienes de Capital para el emisor y el receptor + + + + + Bienes de capital de autoconsumo exento para el emisor + + + + + Bienes de capital sin contraprestación a terceros exento para el emisor + + + + + Sin contraprestación a terceros + + + + + + + Los tipos de descuentos a utilizar en el campo "Código del Descuento" del Descuento + + + + + + + Descuento por Regalía + + + + + Descuento por Regalía IVA Cobrado al Cliente + + + + + Descuento por Bonificación + + + + + Descuento por volumen + + + + + Descuento por Temporada (estacional) + + + + + Descuento promocional + + + + + Descuento Comercial + + + + + Descuento por frecuencia + + + + + Descuento sostenido + + + + + Otros descuentos + + + + + + + + Código del impuesto: + 01 Impuesto al valor agregado, + 02 Impuesto Selectivo de Consumo, + 03 Impuesto único a los combustivos, + 04 Impuesto específico de bebidas alcohólicas, + 05 Impuesto específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador, + 06 Impuesto a los productos de tabaco, + 07 IVA (cálculo especial), + 08 IVA Regimen de Bienes Usados (Factor), + 12 Impuesto Especifico al cemento, + 99 Otros + + + + + + + + Impuesto al Valor Agregado + + + + + Impuesto Selectivo de Consumo + + + + + Impuesto unico a los combustivos + + + + + Impuesto especifico de bebidas alcohólicas + + + + + impuesto especifico sobre las bebidas envasadas sin contenido alcoholico y jabones de tocador + + + + + impuesto a los productos de tabaco + + + + + IVA (cálculo especial) + + + + + IVA Régimen de Bienes Usados (Factor) + + + + + Impuesto Especifico al Cemento + + + + + Otros + + + + + + + Cuando se trata del IVA las tarifas y códigos a utilizar son las siguientes + + + + + + + Tarifa 0% (Artículo 32, num 1, RLIVA) + + + + + Tarifa reducida 1% + + + + + Tarifa reducida 2% + + + + + Tarifa reducida 4% + + + + + Transitorio 0% + + + + + Transitorio 4% + + + + + Tarifa transitoria 8% + + + + + Tarifa general 13% + + + + + Tarifa reducida 0.5% + + + + + Tarifa Exenta + + + + + Tarifa 0% sin derecho a crédito + + + + + + + Tipo de documento de exoneración o de autorización. + + + + + + + Compras autorizadas por la Dirección General de Tributación + + + + + Ventas exentas a diplomáticos + + + + + Autorizado por Ley Especial + + + + + Exenciones Dirección General de Hacienda Autorización Local Genérica + + + + + + Exenciones Dirección General de Hacienda Transitorio V (servicios de ingeniería, arquitectura, topografía obra civil) + + + + + + Servicios turísticos inscritos ante el Instituto Costarricense de Turismo (ICT) + + + + + Transitorio XVII (Recolección, Clasificación, almacenamiento de Reciclaje y reutilizable) + + + + + Exoneración a Zona Franca + + + + + Exoneración de servicios complementarios para la exportación articulo 11 RLIVA + + + + + Órgano de las corporaciones municipales + + + + + Exenciones Dirección General de Hacienda Autorización de Impuesto Local Concreta + + + + + Otros + + + + + + + + Tipo de documento otros cargos + + + + + + Contribución parafiscal + + + + + Timbre de la Cruz Roja + + + + + Timbre de Benemérito Cuerpo de Bomberos de Costa Rica + + + + + Cobro de un tercero + + + + + Costos de Exportación + + + + + Impuesto de servicio 10% + + + + + Timbre de Colegios Profesionales + + + + + Depósitos de Garantía + + + + + Multas o Penalizaciones + + + + + Intereses Moratorios + + + + + Otros Cargos + + + + + + + + + + + Factura electrónica + + + + + Nota de debido electrónica + + + + + nota de crédito electrónica + + + + + Tiquete electrónico + + + + + Nota de despacho + + + + + Contrato + + + + + Procedimiento + + + + + Comprobante emitido en contigencia + + + + + Devolución mercadería + + + + + Comprobante electrónico rechazado por el Ministerio de Hacienda + + + + + Sustituye factura rechazada por el Receptor del comprobante + + + + + Sustituye Factura de exportación + + + + + Facturación mes vencido + + + + + Otros + + + + + Comprobante aportado por contribuyente de Régimen Especial. + + + + + Sustituye una Factura electrónica de Compra + + + + + Comprobante de Proveedor No Domiciliado + + + + + Nota de Crédito a Factura Electrónica de Compra + + + + + Nota de Débito a Factura Electrónica de Compra + + + + + + + + + + + Anula documento de referencia + + + + + Corrige texto de ocumento de referencia + + + + + Referencia a otro documento + + + + + Sustituye comprobante provisional por contigencia + + + + + Devolución de mercancía + + + + + Sustituye comprobante electrónico + + + + + Factura Endosada + + + + + Nota de crédito financiera + + + + + Nota de débito financiera + + + + + Proveedor No Domiciliado + + + + + Crédito por exoneración posterior a la facturación + + + + + Otros + + + + + + + + + + \ No newline at end of file From 5270c6df66361780d8e8abe9488dfe22593b0296 Mon Sep 17 00:00:00 2001 From: David Obando Date: Wed, 27 Aug 2025 19:19:10 -0400 Subject: [PATCH 06/30] ultimos cambios para comparar --- api/contrib/consultar/consultar.php | 15 ++++++++------- api/modules/db/module.php | 26 ++++++++++++++------------ api/modules/files/module.php | 2 ++ www/settings.php | 2 ++ 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/api/contrib/consultar/consultar.php b/api/contrib/consultar/consultar.php index 72c14d77..c6b3e0ef 100644 --- a/api/contrib/consultar/consultar.php +++ b/api/contrib/consultar/consultar.php @@ -33,7 +33,7 @@ function consultarRecepcion() if ($url == null) return "Ha ocurrido un error en el client_id."; - curl_setopt_array($curl, array( + $args = array( CURLOPT_URL => $url . $clave, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", @@ -48,8 +48,9 @@ function consultarRecepcion() "Cache-Control: no-cache", "Content-Type: application/x-www-form-urlencoded" ), - )); + ); + curl_setopt_array($curl, $args); $response = curl_exec($curl); $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); $err = curl_error($curl); @@ -59,7 +60,7 @@ function consultarRecepcion() { $arrayResp = array( "Status" => $status, - "to" => $apiTo, + "to" => $url, "text" => $err ); return $arrayResp; @@ -86,7 +87,6 @@ function consultarRecepcion() } } - function consultarComprobante() { $curl = curl_init(); @@ -104,7 +104,7 @@ function consultarComprobante() if ($url == null) return "Ha ocurrido un error en el client_id."; - curl_setopt_array($curl, array( + $args = array( CURLOPT_URL => $url . $clave, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", @@ -119,8 +119,9 @@ function consultarComprobante() "Cache-Control: no-cache", "Content-Type: application/x-www-form-urlencoded" ), - )); - + ); + + curl_setopt_array($curl, $args); $response = curl_exec($curl); $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); $err = curl_error($curl); diff --git a/api/modules/db/module.php b/api/modules/db/module.php index d1b93b1e..990d4749 100644 --- a/api/modules/db/module.php +++ b/api/modules/db/module.php @@ -48,7 +48,7 @@ */ function db_bootMeUp() { - db_Connect(); + conectarBd(); } //! Test if the connection is good, just for debug @@ -65,21 +65,24 @@ function db_allGood() return true; } -function db_Connect() +function conectarBd() { global $dbConn; # Create connection - grace_debug("config['db']['name']: "); - grace_debug(conf_get('name', 'db')); - grace_debug("config['db']['pwd']: "); - grace_debug(conf_get('pwd', 'db')); - grace_debug("config['db']['user']: "); - grace_debug(conf_get('user', 'db')); - grace_debug("config['db']['host']: "); - grace_debug(conf_get('host', 'db')); + $dbConfig = [ + 'host' => conf_get('host', 'db'), + 'port' => conf_get('port', 'db'), + 'user' => conf_get('user', 'db'), + 'pwd' => conf_get('pwd', 'db'), + 'name' => conf_get('name', 'db') + ]; + + grace_debug(json_encode($dbConfig)); + mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); - @$dbConn = new mysqli(conf_get('host', 'db'), conf_get('user', 'db'), conf_get('pwd', 'db'), conf_get('name', 'db')); + + @$dbConn = new mysqli($dbConfig['host'], $dbConfig['user'], $dbConfig['pwd'], $dbConfig['name'], $dbConfig['port']); # Check connection if ($dbConn->connect_error) @@ -148,6 +151,5 @@ function db_query($q, $return = 1) function db_escape($string = '') { global $dbConn; - return $dbConn->real_escape_string($string); } diff --git a/api/modules/files/module.php b/api/modules/files/module.php index 88ecb309..2d6f685a 100644 --- a/api/modules/files/module.php +++ b/api/modules/files/module.php @@ -185,6 +185,8 @@ function files_upload($type = 'attach', $finalName = false, $ext = false, $maxSi mkdir($targetDir, 0777, true); } + var_dump($_FILES); + die(); # Set the new name if one was given $finalName = ($finalName == false ? basename($_FILES["fileToUpload"]["name"]) : diff --git a/www/settings.php b/www/settings.php index 406d6383..2469bff4 100644 --- a/www/settings.php +++ b/www/settings.php @@ -46,6 +46,8 @@ $config['db']['user'] = getenv('DB_USERNAME'); # Database host $config['db']['host'] = getenv('DB_HOST'); +# Database port +$config['db']['port'] = getenv('DB_PORT') ?: 3306; // Default to 3306 if not set ############################################################################## # # Crypto Keys From 57d295acded3714975e42876fa80b00ca8889033 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 29 Aug 2025 13:27:55 -0400 Subject: [PATCH 07/30] ultimos cambios --- api/contrib/clave/clave.php | 2 +- api/contrib/consultar/consultar.php | 25 +++++++++---- api/contrib/genXML/genXML.php | 56 +++++++++++++++++------------ api/core/params.php | 26 +++----------- api/core/tools.php | 17 ++++++--- api/modules/files/module.php | 4 +-- api/tools/ValidadorXML.php | 16 +++++---- 7 files changed, 79 insertions(+), 67 deletions(-) diff --git a/api/contrib/clave/clave.php b/api/contrib/clave/clave.php index c9b34802..3a52e6cf 100644 --- a/api/contrib/clave/clave.php +++ b/api/contrib/clave/clave.php @@ -85,7 +85,7 @@ function getClave($tipoDocumento = "", $tipoCedula = "", $cedula = "", $situacio } if (!ctype_digit($consecutivo)) { - return "El parametro consecutivo no es numeral"; + return "El parametro consecutivo [$consecutivo] no es numeral"; } else if (strlen($consecutivo) < 10) { $consecutivo = str_pad($consecutivo, 10, "0", STR_PAD_LEFT); } else if (strlen($consecutivo) > 10) { diff --git a/api/contrib/consultar/consultar.php b/api/contrib/consultar/consultar.php index c6b3e0ef..2a6481a5 100644 --- a/api/contrib/consultar/consultar.php +++ b/api/contrib/consultar/consultar.php @@ -31,7 +31,7 @@ function consultarRecepcion() $url = "https://api.comprobanteselectronicos.go.cr/recepcion/v1/recepcion/"; if ($url == null) - return "Ha ocurrido un error en el client_id."; + return "El client_id proprocionado (".params_get("client_id").") no es válido. "; $args = array( CURLOPT_URL => $url . $clave, @@ -65,11 +65,12 @@ function consultarRecepcion() ); return $arrayResp; } - else - { - $response = json_decode($response); - - if(isset($response->{'respuesta-xml'})) { + + $responseObj = json_decode($response); + + if( is_object($responseObj) ) { + // si hay respuesta de hacienda, procesar detalle de mensaje + if(isset($responseObj->{'respuesta-xml'})) { $xmlRespuesta = base64_decode($response->{'respuesta-xml'}); $xmlRespuesta = preg_replace('/>\s+<', $xmlRespuesta); $startLength = strlen("tiene los siguientes errores: ")+7; @@ -83,8 +84,18 @@ function consultarRecepcion() $separadorMensaje = " \n"; $response->{'DetalleMensaje'} = explode($separadorMensaje,trim($detalleMensaje,$separadorMensaje)); } - return $response; + return array( + "Status" => $status, + "to" => $url, + "text" => $responseObj + ); } + + return array( + "Status" => $status, + "to" => $url, + "text" => $response + ); } function consultarComprobante() diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 84341001..4d96e77e 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -162,7 +162,7 @@ function genXMLFe() $registroFiscal8707 = params_get("registrofiscal8707"); // Datos receptor - $omitir_receptor = params_get("omitir_receptor"); // Deprecated + // $omitir_receptor = params_get("omitir_receptor"); // Deprecated $receptorNombre = params_get("receptor_nombre"); $receptorTipoIdentif = params_get("receptor_tipo_identif"); $receptorNumIdentif = params_get("receptor_num_identif"); @@ -184,6 +184,8 @@ function genXMLFe() $plazoCredito = params_get("plazo_credito"); $codMoneda = params_get("cod_moneda"); $tipoCambio = params_get("tipo_cambio"); + + /* $totalServGravados = params_get("total_serv_gravados"); $totalServExentos = params_get("total_serv_exentos"); $totalServExonerado = params_get("total_serv_exonerados"); @@ -197,14 +199,16 @@ function genXMLFe() $totalExonerado = params_get("total_exonerado"); $totalNoSujeto = params_get("total_no_sujeto"); $totalVenta = params_get("total_ventas"); - $totalDescuentos = params_get("total_descuentos"); $totalVentasNeta = params_get("total_ventas_neta"); $totalImp = params_get("total_impuestos"); + $totalComprobante = params_get("total_comprobante"); + */ + + $totalDescuentos = params_get("total_descuentos"); $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); $totalIVADevuelto = params_get("totalIVADevuelto"); $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); - + $otros = json_decode(params_get('otros')); // Detalles de la compra @@ -266,7 +270,10 @@ function genXMLFe() if (!in_array($condVenta, ['02', '08', '10'], true)) { tools_reply([ "Status" => 400, - "text" => "El campo 'medios_pago' es obligatorio cuando 'condicion_venta' no es '02', '08' o '10'", + "text" => [ + "medios_pago" => $mediosPago, + "mensaje" => "El campo 'medios_pago' es obligatorio cuando 'condicion_venta' no es '02', '08' o '10'" + ] ], true); } } @@ -309,19 +316,18 @@ function genXMLFe() $xmlString .= ' ' . $emisorNombreComercial . ''; } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - + + if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito) { + $xmlString .= ' ' . $emisorProv . ' ' . $emisorCanton . ' ' . $emisorDistrito . ''; if ($emisorBarrio != '') { $xmlString .= '' . $emisorBarrio . ''; } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; + $xmlString .= '123456'; + + $xmlString .= ''; } if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { @@ -333,12 +339,12 @@ function genXMLFe() } if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; + $xmlString .= '' . trim($emisorEmail) . ''; } else { error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); } - $xmlString .= ' + $xmlString .= ' ' . $receptorNombre . ''; $xmlString .= ' @@ -1345,17 +1351,18 @@ function genXMLNC() } if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - + $xmlString .= ' ' . $emisorProv . ' ' . $emisorCanton . ' ' . $emisorDistrito . ''; + if ($emisorBarrio != '') { $xmlString .= '' . $emisorBarrio . ''; } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; + if ($emisorOtrasSenas != '') { + $xmlString .= '' . $emisorOtrasSenas . ''; + } + $xmlString .= ''; } if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { @@ -2091,7 +2098,7 @@ function genXMLND() $emisorTel = params_get("emisor_tel"); $emisorEmail = params_get("emisor_email"); $registroFiscal8707 = params_get("registrofiscal8707"); - + // Datos receptor $omitir_receptor = params_get("omitir_receptor"); // Deprecated $receptorNombre = params_get("receptor_nombre"); @@ -3904,7 +3911,7 @@ function genXMLFec() $registroFiscal8707 = params_get("registrofiscal8707"); // Datos receptor - $omitir_receptor = params_get("omitir_receptor"); // Deprecated + // Deprecated - $omitir_receptor = params_get("omitir_receptor"); $receptorNombre = params_get("receptor_nombre"); $receptorTipoIdentif = params_get("receptor_tipo_identif"); $receptorNumIdentif = params_get("receptor_num_identif"); @@ -4008,7 +4015,7 @@ function genXMLFec() $mediosPago = array_slice($mediosPago, 0, 4); } } - + $xmlString = ' '; } else { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); + tools_reply([ + "Status" => 400, + "text" => "El email del emisor ($emisorEmail) no tiene un formato válido.", + ], true); } diff --git a/api/core/params.php b/api/core/params.php index 60210784..873fa4bc 100644 --- a/api/core/params.php +++ b/api/core/params.php @@ -38,33 +38,17 @@ function params_get($p, $def = false) /** * Set config parameters */ -function params_set($p, $val = false, $override = false) +function params_set($p, $val = false) { global $params; - if (is_array($val)) { foreach ($val as $vv => $v) { - _params_set($vv, $v, $override); + $params[$vv] = $v; } - } else - _params_set($p, $val, $override); - - return $params[$p]; -} - -/** - * Helper function to actually store the params - */ -function _params_set($p, $val = false, $override = false) -{ - global $params; - - if (isset($params[$p]) && $override) - $params[$p] = $val; - else + } else { $params[$p] = $val; - - return $params[$p]; + } + return is_array($val) ? $val : $params[$p]; } /** diff --git a/api/core/tools.php b/api/core/tools.php index 9331d95f..b6200486 100644 --- a/api/core/tools.php +++ b/api/core/tools.php @@ -85,10 +85,18 @@ function tools_reply($response, $killMe = false) } if (params_get('replyType', 'json') == 'json') { - _tools_reply(tools_returnJson(array( - 'status' => ($killMe ? 'error' : 'ok'), - 'resp' => $response - ))); + if (conf_get('mode', 'core', 'cli') == 'cli') { + _tools_reply(tools_returnJson(array( + 'status' => ($killMe ? 'error' : 'ok'), + 'resp' => $response + ))); + } else { + return tools_returnJson(array( + 'status' => ($killMe ? 'error' : 'ok'), + 'resp' => $response + )); + } + # There will be other reply types soon... //} //elseif(params_get('replyType', 'json') == 'plain') @@ -122,7 +130,6 @@ function tools_returnJson($response, $addHeaders = true) header('Content-Type: text/html; charset=utf-8'); header('Content-Type: application/json'); } - return json_encode($response); } diff --git a/api/modules/files/module.php b/api/modules/files/module.php index 2d6f685a..a6f58c8c 100644 --- a/api/modules/files/module.php +++ b/api/modules/files/module.php @@ -156,7 +156,7 @@ function files_upload($type = 'attach', $finalName = false, $ext = false, $maxSi global $user; # Load the tool - tools_useTool('ImageResize.php'); + // tools_useTool('ImageResize.php'); grace_debug("Uploading a file"); @@ -185,8 +185,6 @@ function files_upload($type = 'attach', $finalName = false, $ext = false, $maxSi mkdir($targetDir, 0777, true); } - var_dump($_FILES); - die(); # Set the new name if one was given $finalName = ($finalName == false ? basename($_FILES["fileToUpload"]["name"]) : diff --git a/api/tools/ValidadorXML.php b/api/tools/ValidadorXML.php index 512bc1f4..885d886c 100644 --- a/api/tools/ValidadorXML.php +++ b/api/tools/ValidadorXML.php @@ -43,18 +43,20 @@ private static function libxml_display_errors() public static function validateXml($contenido_xml, $consecutivo, $version = "4.4") { $tipo_doc = substr($consecutivo, 8, 2); + $baseFolder = conf_get('coreInstall', 'modules', ''); + $baseFolder = str_replace('/api', '/www', $baseFolder); $schemas = [ - "01" => "xsd/FacturaElectronica_V$version.xsd", - "02" => "xsd/FacturaCompra_V$version.xsd", - "03" => "xsd/NotaCreditoElectronica_V$version.xsd", - "04" => "xsd/TiqueteElectronico_V$version.xsd", - "05" => "xsd/MensajeHacienda_V$version.xsd", + "01" => "$baseFolder/xsd/FacturaElectronica_V$version.xsd", + "02" => "$baseFolder/xsd/FacturaCompra_V$version.xsd", + "03" => "$baseFolder/xsd/NotaCreditoElectronica_V$version.xsd", + "04" => "$baseFolder/xsd/TiqueteElectronico_V$version.xsd", + "05" => "$baseFolder/xsd/MensajeHacienda_V$version.xsd", ]; if (empty($contenido_xml) || empty($tipo_doc)) { return (object) [ "status" => "error", - "message" => "Error: falta el contenido del XML o el tipo de documento", + "message" => "Error: falta el contenido del XML o el tipo de documento ($tipo_doc) al validar.", ]; } @@ -68,7 +70,7 @@ public static function validateXml($contenido_xml, $consecutivo, $version = "4.4 if (!file_exists($schemas[$tipo_doc])) { return (object) [ "status" => "error", - "message" => "Error: no se encuentra el esquema " . $schemas[$tipo_doc] . " definido para el tipo $tipo_doc, v$version", + "message" => "Error: no existe el archivo XSD " . $schemas[$tipo_doc] . " definido para el tipo $tipo_doc, v$version", ]; } From 444bbb6ae3eb02a9232ee6c116f6d3d1290e3b26 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 29 Aug 2025 13:38:21 -0400 Subject: [PATCH 08/30] deshacer cambio en tool_reply --- api/core/tools.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/api/core/tools.php b/api/core/tools.php index b6200486..8a273be0 100644 --- a/api/core/tools.php +++ b/api/core/tools.php @@ -85,17 +85,10 @@ function tools_reply($response, $killMe = false) } if (params_get('replyType', 'json') == 'json') { - if (conf_get('mode', 'core', 'cli') == 'cli') { - _tools_reply(tools_returnJson(array( - 'status' => ($killMe ? 'error' : 'ok'), - 'resp' => $response - ))); - } else { - return tools_returnJson(array( - 'status' => ($killMe ? 'error' : 'ok'), - 'resp' => $response - )); - } + _tools_reply(tools_returnJson(array( + 'status' => ($killMe ? 'error' : 'ok'), + 'resp' => $response + ))); # There will be other reply types soon... //} From ce227fc030e0158aea79249bdfe8639aabc184e6 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 29 Aug 2025 13:40:47 -0400 Subject: [PATCH 09/30] fix: error en el detalle de respuesta --- api/contrib/consultar/consultar.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/contrib/consultar/consultar.php b/api/contrib/consultar/consultar.php index 2a6481a5..4a6b9de8 100644 --- a/api/contrib/consultar/consultar.php +++ b/api/contrib/consultar/consultar.php @@ -71,7 +71,7 @@ function consultarRecepcion() if( is_object($responseObj) ) { // si hay respuesta de hacienda, procesar detalle de mensaje if(isset($responseObj->{'respuesta-xml'})) { - $xmlRespuesta = base64_decode($response->{'respuesta-xml'}); + $xmlRespuesta = base64_decode($responseObj->{'respuesta-xml'}); $xmlRespuesta = preg_replace('/>\s+<', $xmlRespuesta); $startLength = strlen("tiene los siguientes errores: ")+7; $startPos = strpos($xmlRespuesta, "tiene los siguientes errores: ", 0); @@ -82,7 +82,7 @@ function consultarRecepcion() $endPos = strpos($xmlRespuesta, "", $startPos); $detalleMensaje = substr($xmlRespuesta, $startPos+$startLength, $endPos -($startPos+$startLength+1)); $separadorMensaje = " \n"; - $response->{'DetalleMensaje'} = explode($separadorMensaje,trim($detalleMensaje,$separadorMensaje)); + $responseObj->{'DetalleMensaje'} = explode($separadorMensaje,trim($detalleMensaje,$separadorMensaje)); } return array( "Status" => $status, From 0a0456139724e4abcca7ad177e1f55e9e4b47b07 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 29 Aug 2025 15:33:38 -0400 Subject: [PATCH 10/30] fix: error en modulo consultar cuando el comprobante no ha sido enviado --- api/contrib/consultar/consultar.php | 36 ++++++++++++++++++++++++----- api/core/params.php | 20 ++++++++++++---- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/api/contrib/consultar/consultar.php b/api/contrib/consultar/consultar.php index 4a6b9de8..6905ff4a 100644 --- a/api/contrib/consultar/consultar.php +++ b/api/contrib/consultar/consultar.php @@ -36,6 +36,7 @@ function consultarRecepcion() $args = array( CURLOPT_URL => $url . $clave, CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_SSL_VERIFYHOST => 0, @@ -52,38 +53,45 @@ function consultarRecepcion() curl_setopt_array($curl, $args); $response = curl_exec($curl); + + $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE); + $headers = substr($response, 0, $header_size); + $body = substr($response, $header_size); + $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); $err = curl_error($curl); curl_close($curl); - - if ($err) - { + + if ($err) { $arrayResp = array( "Status" => $status, "to" => $url, "text" => $err ); + return $arrayResp; } - $responseObj = json_decode($response); + $responseObj = json_decode($body); if( is_object($responseObj) ) { // si hay respuesta de hacienda, procesar detalle de mensaje if(isset($responseObj->{'respuesta-xml'})) { + $xmlRespuesta = base64_decode($responseObj->{'respuesta-xml'}); $xmlRespuesta = preg_replace('/>\s+<', $xmlRespuesta); $startLength = strlen("tiene los siguientes errores: ")+7; $startPos = strpos($xmlRespuesta, "tiene los siguientes errores: ", 0); if(!$startPos) { // si no hay errores, retornar la respuesta normal - return $response; + return $responseObj; } $endPos = strpos($xmlRespuesta, "", $startPos); $detalleMensaje = substr($xmlRespuesta, $startPos+$startLength, $endPos -($startPos+$startLength+1)); $separadorMensaje = " \n"; $responseObj->{'DetalleMensaje'} = explode($separadorMensaje,trim($detalleMensaje,$separadorMensaje)); } + return array( "Status" => $status, "to" => $url, @@ -91,10 +99,26 @@ function consultarRecepcion() ); } + if(empty($body)) { + $header_lines = explode("\r\n", trim($headers)); + $header_array = []; + foreach ($header_lines as $line) { + if (strpos($line, ':') !== false) { + list($key, $value) = explode(': ', $line, 2); + $header_array[$key] = $value; + } + } + return array( + "Status" => $header_array['x-http-status'], + "to" => $url, + "text" => $header_array['x-error-cause'] + ); + } + return array( "Status" => $status, "to" => $url, - "text" => $response + "text" => $body ); } diff --git a/api/core/params.php b/api/core/params.php index 873fa4bc..20258df1 100644 --- a/api/core/params.php +++ b/api/core/params.php @@ -36,21 +36,31 @@ function params_get($p, $def = false) } /** - * Set config parameters + * Set config parameters. + * - When $val is array, performs a bulk set. + * - $override=true overwrites existing keys; false preserves existing values. + * Returns the persisted value(s) from $params. */ -function params_set($p, $val = false) +function params_set($p, $val = false, $override = true) { global $params; + if (is_array($val)) { foreach ($val as $vv => $v) { - $params[$vv] = $v; + if ($override || !array_key_exists($vv, $params)) { + $params[$vv] = $v; + } } - } else { + // Return the slice that reflects what is now stored + return array_intersect_key($params, $val); + } + if ($override || !array_key_exists($p, $params)) { $params[$p] = $val; } - return is_array($val) ? $val : $params[$p]; + return array_key_exists($p, $params) ? $params[$p] : null; } + /** * Verify request and set default values when required * @todo Verify possible options for each a|b|c etc... From 967f82ae9e9d03975da11c0849842702cd29ab62 Mon Sep 17 00:00:00 2001 From: David Obando Date: Thu, 4 Sep 2025 00:45:22 -0400 Subject: [PATCH 11/30] ultimos cambios, validaciones y calculos automaticos mejorados --- api/contrib/clave/clave.php | 7 +- api/contrib/crlibreall/module.php | 2 +- api/contrib/genXML/genXML.php | 260 +++++++++++++++++++++--------- api/contrib/genXML/module.php | 12 +- api/core/tools.php | 53 +++--- 5 files changed, 217 insertions(+), 117 deletions(-) diff --git a/api/contrib/clave/clave.php b/api/contrib/clave/clave.php index 3a52e6cf..ec41b1b7 100644 --- a/api/contrib/clave/clave.php +++ b/api/contrib/clave/clave.php @@ -54,9 +54,8 @@ function getClave($tipoDocumento = "", $tipoCedula = "", $cedula = "", $situacio $sucursal = params_get("sucursal", "001"); $terminal = params_get("terminal", "00001"); - $dia = date('d'); - $mes = date('m'); - $ano = date('y'); + $date = new DateTime('now', new DateTimeZone('America/Costa_Rica')); + $fechaClave = $date->format('dmy'); // Validamos el parametro de cedula if (!ctype_digit($cedula)) @@ -187,7 +186,7 @@ function getClave($tipoDocumento = "", $tipoCedula = "", $cedula = "", $situacio } // Crea la clave - $clave = $codigoPais . $dia . $mes . $ano . $identificacion . $consecutivoFinal . $codSituacion . $codigoSeguridad; + $clave = $codigoPais . $fechaClave . $identificacion . $consecutivoFinal . $codSituacion . $codigoSeguridad; $arrayResp = array( "clave" => "$clave", "consecutivo" => "$consecutivoFinal", diff --git a/api/contrib/crlibreall/module.php b/api/contrib/crlibreall/module.php index f94790df..bcd78fe6 100644 --- a/api/contrib/crlibreall/module.php +++ b/api/contrib/crlibreall/module.php @@ -110,7 +110,7 @@ function crlibreall_init() array("key" => "total_comprobante", "def" => "", "req" => true), array("key" => "otros", "def" => "", "req" => false), array("key" => "detalles", "def" => "", "req" => true), - array("key" => "informacion_referencia", "def" => "", "req" => true), + array("key" => "informacion_referencia", "def" => "", "req" => false), array("key" => "otrosCargos", "def" => "", "req" => false) ), 'file' => 'genXML.php' diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 4d96e77e..38511ffc 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -32,7 +32,9 @@ tools_useTool('ValidadorXML.php'); -const AUTO_CALCULAR = ["totalServGravados", "totalServExentos", "totalServExonerado", "totalServNoSujeto", "totalMercanciasGravadas", "totalMercanciasExentas", "totalMercExonerada", "totalMercNoSujeta", "totalGravado", "totalExento", "totalExonerado", "totalNoSujeto", "totalVenta", "totalDescuentos", "totalVentaNeta","totalImpuesto"]; +const AUTO_CALCULAR = ["totalServGravados", "totalServExentos", "totalServExonerado", "totalServNoSujeto", "totalMercanciasGravadas", "totalMercanciasExentas", "totalMercExonerada", "totalMercNoSujeta", "totalGravado", "totalExento", "totalExonerado", "totalNoSujeto", "totalVenta", "totalDescuentos", "totalVentaNeta","totalDesgloseImpuesto", "totalImpuesto"]; + +define("DECIMALES", 5); /** * Devuelve true si todas las claves existen en el array @@ -86,52 +88,60 @@ function validarLinea($numLinea, $d) { */ function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) { + if(!isset($calculados['totalDescuentos'])) { + $calculados['totalDescuentos'] = 0; + } + $calculados['totalDescuentos'] += round($totalDescuentos, DECIMALES); + if(!isset($calculados[$numLinea])) { - $calculados[$numLinea] = [ - "impuestoNeto" => 0, - "montoTotalLinea" => 0, // sumatoria de los campos “Subtotal”, “Impuesto Neto”. - "baseImponible" => 0, - "montoTotal" => 0, // multiplicar el campo cantidad por el campo precio unitario - "subTotal" => 0 - ]; + $calculados[$numLinea] = []; } // Se obtiene de la resta del campo monto total menos monto de descuento concedido - $calculados[$numLinea]['montoTotal'] = $d->precioUnitario * $d->cantidad; - $calculados[$numLinea]['subTotal'] = $calculados[$numLinea]['montoTotal'] - $totalDescuentos; + $calculados[$numLinea]['montoTotal'] = round($d->precioUnitario * $d->cantidad, DECIMALES); + $calculados[$numLinea]['subTotal'] = round($calculados[$numLinea]['montoTotal'] - $totalDescuentos, DECIMALES); - // Se calcula de la siguiente manera: se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. - - $calculados[$numLinea]['montoTotalLinea'] += ($calculados[$numLinea]['subTotal'] + $calculados[$numLinea]['montoTotalLinea']); - - // impuesto.Codigo = 02, 04, 05, 12 no considerados aun - // impuesto.codigo = 01 + tarifa - if(!isset($d->impuesto) || !is_array($d->impuesto)) { - tools_reply("Al auto-calcular baseImponible, detalle de líneas $numLinea: es obligatorio enviar el campo 'impuesto'", true); - } - - $calculados[$numLinea]['baseImponible'] = $calculados[$numLinea]['subTotal']; - /* - baseImponible: - Se obtiene de la suma entre el campo "Subtotal", más - el impuesto selectivo de consumo (02), - el Impuesto específico de Bebidas Alcohólicas (04), - el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05) y - el impuesto al cemento (12), cuando corresponda. - Este campo se podrá editar cuando se seleccione - en el campo "IVA cobrado a nivel de fábrica" el Código 01 - o en el campo de "Código del impuesto" el código 07. + @TODO base imponible puede incluir + Este campo será de condición obligatoria, cuando el producto/ servicio este gravado con algún impuesto. + Se obtiene de la suma entre el campo "Subtotal", más + el impuesto selectivo de consumo (02), + el Impuesto específico de Bebidas Alcohólicas (04), + el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05) y + el impuesto al cemento (12), cuando corresponda. + Este campo se podrá editar cuando se seleccione + en el campo "IVA cobrado a nivel de fábrica" el Código 01 + o en el campo de "Código del impuesto" el código 07. */ + $calculados[$numLinea]['baseImponible'] = $calculados[$numLinea]['subTotal']; + foreach(AUTO_CALCULAR as $field) { if(!isset($calculados[$field])) { - $calculados[$field] = 0; + if($field !== 'totalDesgloseImpuesto') { + $calculados[$field] = 0; + continue; + } + $calculados[$field] = []; } } } +/** + * Elimina caracteres no permitidos + * Solo letras, numeros, espacios y signos de puntuacion comunes en detalle + */ +function limpiarTexto($texto, $largoMaximo=FALSE) { + $limpio = preg_replace('/[^\\p{L}\\p{N}\\s.,;:\\-\\_\\(\\)\\[\\]\\{\\}\\!\\?\'"\\n\\r]/u', '', $texto); + + if($largoMaximo === FALSE) { + return $limpio; + } + // Trunca a la longitud máxima permitida + return mb_substr($limpio, 0, $largoMaximo); +} + /* * ************************************************** */ /* Funcion para generar XML */ /* * ************************************************** */ @@ -204,7 +214,7 @@ function genXMLFe() $totalComprobante = params_get("total_comprobante"); */ - $totalDescuentos = params_get("total_descuentos"); + // $totalDescuentos = params_get("total_descuentos"); $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); $totalIVADevuelto = params_get("totalIVADevuelto"); $totalOtrosCargos = params_get("totalOtrosCargos"); @@ -411,6 +421,14 @@ function genXMLFe() if (isset($plazoCredito) && $plazoCredito != "") { $xmlString .= ' ' . $plazoCredito . ''; + } else { + // es credito segun la condicion de venta + if($condVenta == "02") { + tools_reply([ + "Status" => 400, + "text" => "El campo 'plazoCredito' es obligatorio cuando 'condicionVenta' es '02'" + ], true); + } } $xmlString .= ' @@ -598,6 +616,9 @@ function genXMLFe() $xmlString .= ' ' . $d->unidadMedidaComercial . ''; } + + $d->detalle = limpiarTexto($d->detalle, 150); + $xmlString .= '' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { $xmlString .= '' . $d->numeroVINoSerie . ''; @@ -693,10 +714,11 @@ function genXMLFe() $xmlString .= ''; } - - $totalDescuentos = 0; + $xmlStringDescuento = ''; + // Procesar descuentos, si existen if (isset($d->descuento) && !empty($d->descuento)) { + $descuentoArray = (array)$d->descuento; if (count($descuentoArray) > 5) { @@ -711,7 +733,8 @@ function genXMLFe() isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" ) { - $xmlString .= ' + $c['montoDescuento'] = floatval($c['montoDescuento']) * $d->cantidad; + $xmlStringDescuento = ' ' . $c['montoDescuento'] . ' ' . $c['codigoDescuento'] . ''; // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo @@ -720,17 +743,17 @@ function genXMLFe() isset($c['codigoDescuentoOTRO']) && strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; + $xmlStringDescuento .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; } // NaturalezaDescuento: minOccurs=0, longitud 3-80 if ( isset($c['naturalezaDescuento']) && strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; + $xmlStringDescuento .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; } - $xmlString .= ''; - $totalDescuentos += floatval($c['montoDescuento']); + $xmlStringDescuento .= ''; + $totalDescuentos += $c['montoDescuento']; } } } @@ -740,6 +763,7 @@ function genXMLFe() $xmlString .= '' . $d->precioUnitario . ''; $xmlString .= '' . $calculados[$l]['montoTotal'] . ''; + $xmlString .= $xmlStringDescuento; $xmlString .= '' . $calculados[$l]['subTotal'] . ''; if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { @@ -778,8 +802,8 @@ function genXMLFe() "10" => 0, // Tarifa Exenta "11" => 0 // Tarifa 0% sin derecho a crédito ]; - $calculados[$l]['tarifa'] = round($impuestoPorTarifa[$i->codigoTarifa], 2)*100; - $calculados[$l]['monto'] = round($calculados[$l]['baseImponible'] * $impuestoPorTarifa[$i->codigoTarifa], 2); + $calculados[$l]['tarifa'] = $impuestoPorTarifa[$i->codigoTarifa] * 100; + $calculados[$l]['monto'] = round(floatval($calculados[$l]['baseImponible']) * $impuestoPorTarifa[$i->codigoTarifa], DECIMALES); } if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { @@ -857,22 +881,25 @@ function genXMLFe() if(isset($i->codigo) && isset($i->monto)) { // Se suma el impuesto neto a totalImpuestos - $calculados[$l]['impuestoNeto'] += $i->monto; - + if(!isset($calculados[$l]['impuestoNeto'])) { + $calculados[$l]['impuestoNeto'] = 0; + } + $calculados[$l]['impuestoNeto'] += round($i->monto, DECIMALES); + if($i->codigoTarifa === "10" || $i->codigoTarifa === "11") { - // Exento + // Exento $resumenKeys = [ "servicio" => "totalServExentos", "mercancia" => "totalMercanciasExentas", ]; - $calculados['totalExento'] += $calculados[$l]['baseImponible']; + $calculados['totalExento'] += ($calculados[$l]['montoTotal']); } else { // Gravado $resumenKeys = [ "servicio" => "totalServGravados", "mercancia" => "totalMercanciasGravadas", ]; - $calculados['totalGravado'] += $calculados[$l]['baseImponible']; + $calculados['totalGravado'] += ($calculados[$l]['montoTotal']); } // Se suma el impuesto neto a totalImpuestos @@ -880,15 +907,31 @@ function genXMLFe() // los codigos que empiezan de 0 a 4 son mercaderias y los que empiezan de 5 a 9 son servicios if(isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5','6','7','8','9'], true)) { // Servicio - $calculados[$resumenKeys['servicio']] += $calculados[$l]['baseImponible']; + $calculados[$resumenKeys['servicio']] += ($calculados[$l]['montoTotal']); } else { // Mercancia - $calculados[$resumenKeys['mercancia']] += $calculados[$l]['baseImponible']; + $calculados[$resumenKeys['mercancia']] += ($calculados[$l]['montoTotal']); } $calculados['totalImpuesto'] += $i->monto; - $calculados['totalVenta'] += $calculados[$l]['baseImponible']; - + + // Desglose de impuestos por código y tarifa + $tdiKey = $i->codigo . "-" . $i->codigoTarifa; + + if(!isset($calculados['totalDesgloseImpuesto'][$tdiKey])) { + $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ + "Codigo" => $i->codigo, + "CodigoTarifaIVA" => $i->codigoTarifa, + "TotalMontoImpuesto" => $i->monto + ]; + } else { + $_totalMontoImpuesto = $calculados['totalDesgloseImpuesto'][$tdiKey]->TotalMontoImpuesto; + $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ + "Codigo" => $i->codigo, + "CodigoTarifaIVA" => $i->codigoTarifa, + "TotalMontoImpuesto" => $i->monto + $_totalMontoImpuesto + ]; + } } } } @@ -901,6 +944,9 @@ function genXMLFe() $xmlString .= '' . $calculados[$l]['impuestoNeto'] . ''; + // Se calcula de la siguiente manera: se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. + $calculados[$l]['montoTotalLinea'] = $calculados[$l]['subTotal'] + $calculados[$l]['impuestoNeto']; + $xmlString .= '' . $calculados[$l]['montoTotalLinea'] . ''; $xmlString .= ''; @@ -932,6 +978,7 @@ function genXMLFe() $xmlString .= ' ' . $o->nombreTercero . ''; } + $o->detalle = limpiarTexto($o->detalle, 150); $xmlString .= ' ' . $o->detalle . ''; if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { @@ -945,6 +992,12 @@ function genXMLFe() } } + foreach ($calculados as $key => $value) { + if(is_numeric($value)) { + $calculados[$key] = round($value, DECIMALES); + } + } + $xmlString .= ' '; @@ -962,7 +1015,24 @@ function genXMLFe() '; } + // Se obtiene de la suma de los campos "Total servicios No Sujetos de IVA" mas "Total mercancías No Sujetas de IVA". + // @TODO revisar no sujetos + // $calculados['totalNoSujeto'] = $calculados['totalServNoSujetos'] + $calculados['totalMercanciasNoSujetas']; + + // Se obtiene de la suma de los campos "total servicios exonerados de IVA" mas "total de mercancías exoneradas del IVA". + // @TODO revisar exoneraciones + // $calculados['totalExonerado'] = $calculados['totalServExonerados'] + $calculados['totalMercanciasExoneradas']; + + // Calcular totalVenta + // Se obtiene de la sumatoria de los campos “total gravado”, “total exento”, “Total Exonerado” y “Total No Sujeto + $calculados['totalVenta'] = + $calculados['totalGravado'] + + $calculados['totalExento'] + + $calculados['totalExonerado'] + + $calculados['totalNoSujeto']; + // Calcular totalVentaNeta + // Se obtiene de la resta de los campos total venta menos total descuento $calculados['totalVentaNeta'] = $calculados['totalVenta'] - $calculados['totalDescuentos']; // totalComprobante: Se obtiene de la suma de los campos "total venta neta", "monto total del impuesto" y "total otros cargos" menos "total IVA devuelto", en caso de contar con dichos campos. @@ -976,29 +1046,33 @@ function genXMLFe() // agrega los campos del resumen que esten en el array calculados y no esten vacios foreach (AUTO_CALCULAR as $campoResumen) { - if($calculados[$campoResumen]!='') { - $xmlString .= '<' . ucfirst($campoResumen) . '>' . $calculados[$campoResumen] . ''; + if($campoResumen == "totalDesgloseImpuesto") { + // Add logic for TotalDesgloseImpuesto + $totalDesgloseImpuesto = $calculados['totalDesgloseImpuesto']?? []; + if (count($totalDesgloseImpuesto) > 0) { + foreach ($totalDesgloseImpuesto as $impuesto) { + $xmlString .= ' + '; + if (isset($impuesto->Codigo)) { + $xmlString .= '' . $impuesto->Codigo . ''; + } + if (isset($impuesto->CodigoTarifaIVA)) { + $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; + } + if (isset($impuesto->TotalMontoImpuesto)) { + $xmlString .= '' . round($impuesto->TotalMontoImpuesto, DECIMALES) . ''; + } + $xmlString .= ''; + } + } + continue; } - } - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; + if($calculados[$campoResumen]!='') { + $xmlString .= '<' . ucfirst($campoResumen) . '>' . $calculados[$campoResumen] . ''; } } - + if ($totalImpAsumidoEmisorFabrica != '') { $xmlString .= ' ' . $totalImpAsumidoEmisorFabrica . ''; @@ -1032,20 +1106,21 @@ function genXMLFe() // Add TotalMedioPago if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; + $xmlString .= '' . round($o->totalMedioPago, DECIMALES) . ''; } $xmlString .= ''; $totalMediosPago += floatval($o->totalMedioPago); } - if ($totalMediosPago != $calculados['totalComprobante']) { - tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: $totalMediosPago, Total Comprobante: " . $calculados['totalComprobante'], true); + + if (round((float)$totalMediosPago, 2) !== round((float)$calculados['totalComprobante'], 2)) { + tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [" . (float)$totalMediosPago . "], Total Comprobante: [" . (float)$calculados['totalComprobante'] ."] XML: $xmlString", true); } } - $xmlString .= ' - ' . $calculados['totalComprobante'] . ' - '; + $xmlString .= '' . $calculados['totalComprobante'] . ''; + + $xmlString .= ' '; if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { foreach ($informacionReferencia as $ref) { @@ -1168,7 +1243,19 @@ function genXMLFe() $validacion = ValidadorXML::validateXml($xmlString, $consecutivo); - if($validacion && $validacion->status === 'error') { + + + if($validacion && $validacion->status == "error") { + if(conf_get('mode', 'core', 'web') == 'cli') { + return tools_reply([ + "Status" => 400, + "text" => array( + "clave" => $clave, + "validacion" => $validacion + ) + ], true); + } + tools_reply([ "Status" => 400, "text" => array( @@ -1184,6 +1271,7 @@ function genXMLFe() ); } + function genXMLNC() { @@ -1253,7 +1341,7 @@ function genXMLNC() $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); $totalIVADevuelto = params_get("totalIVADevuelto"); $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); + // $totalComprobante = params_get("total_comprobante"); $otros = json_decode(params_get('otros')); @@ -1376,7 +1464,10 @@ function genXMLNC() if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { $xmlString .= '' . trim($emisorEmail) . ''; } else { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); + return tools_reply([ + "Status" => 400, + "text" => "El correo del emisor no cumple con el formato establecido." + ], true); } if ($omitir_receptor != 'true') { @@ -1511,6 +1602,7 @@ function genXMLNC() $xmlString .= ' ' . $d->unidadMedidaComercial . ''; } + $d->detalle = limpiarTexto($d->detalle, 150); $xmlString .= ' ' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { @@ -1777,6 +1869,7 @@ function genXMLNC() $xmlString .= ' ' . $o->nombreTercero . ''; } + $o->detalle = limpiarTexto($o->detalle, 150); $xmlString .= ' ' . $o->detalle . ''; if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { @@ -2396,6 +2489,7 @@ function genXMLND() $xmlString .= ' ' . $d->unidadMedidaComercial . ''; } + $d->detalle = limpiarTexto($d->detalle, 150); $xmlString .= ' ' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { @@ -2662,6 +2756,7 @@ function genXMLND() $xmlString .= ' ' . $o->nombreTercero . ''; } + $o->detalle = limpiarTexto($o->detalle, 150); $xmlString .= ' ' . $o->detalle . ''; if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { @@ -3266,6 +3361,7 @@ function genXMLTE() $xmlString .= ' ' . $d->unidadMedidaComercial . ''; } + $d->detalle = limpiarTexto($d->detalle, 150); $xmlString .= ' ' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { @@ -3532,6 +3628,7 @@ function genXMLTE() $xmlString .= ' ' . $o->nombreTercero . ''; } + $o->detalle = limpiarTexto($o->detalle, 150); $xmlString .= ' ' . $o->detalle . ''; if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { @@ -4197,6 +4294,7 @@ function genXMLFec() $xmlString .= ' ' . $d->unidadMedidaComercial . ''; } + $d->detalle = limpiarTexto($d->detalle, 150); $xmlString .= ' ' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { @@ -4345,6 +4443,7 @@ function genXMLFec() $xmlString .= ' ' . $o->nombreTercero . ''; } + $o->detalle = limpiarTexto($o->detalle, 150); $xmlString .= ' ' . $o->detalle . ''; if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { @@ -4913,6 +5012,7 @@ function genXMLFee() $xmlString .= ' ' . $d->unidadMedidaComercial . ''; } + $d->detalle = limpiarTexto($d->detalle, 150); $xmlString .= ' ' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { @@ -5150,6 +5250,7 @@ function genXMLFee() $xmlString .= ' ' . $o->nombreTercero . ''; } + $o->detalle = limpiarTexto($o->detalle, 150); $xmlString .= ' ' . $o->detalle . ''; if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { @@ -5480,7 +5581,6 @@ function genXMLFee() ); } - /* * ************************************************** */ /* Funcion de prueba */ /* * ************************************************** */ diff --git a/api/contrib/genXML/module.php b/api/contrib/genXML/module.php index bcf071aa..9d63c05d 100644 --- a/api/contrib/genXML/module.php +++ b/api/contrib/genXML/module.php @@ -118,7 +118,7 @@ function genXML_init() array("key" => "total_comprobante", "def" => "", "req" => true), array("key" => "otros", "def" => "", "req" => false), array("key" => "detalles", "def" => "", "req" => true), - array("key" => "informacion_referencia", "def" => "", "req" => true), + array("key" => "informacion_referencia", "def" => "", "req" => false), array("key" => "otrosCargos", "def" => "", "req" => false) ), 'file' => 'genXML.php' @@ -193,7 +193,7 @@ function genXML_init() array("key" => "otros", "def" => "", "req" => false), array("key" => "otrosType", "def" => "", "req" => false), array("key" => "detalles", "def" => "", "req" => true), - array("key" => "informacion_referencia", "def" => "", "req" => true), + array("key" => "informacion_referencia", "def" => "", "req" => false), array("key" => "otrosCargos", "def" => "", "req" => false) ), 'file' => 'genXML.php' @@ -268,7 +268,7 @@ function genXML_init() array("key" => "otros", "def" => "", "req" => false), array("key" => "otrosType", "def" => "", "req" => false), array("key" => "detalles", "def" => "", "req" => true), - array("key" => "informacion_referencia", "def" => "", "req" => true), + array("key" => "informacion_referencia", "def" => "", "req" => false), array("key" => "otrosCargos", "def" => "", "req" => false) ), 'file' => 'genXML.php' @@ -342,7 +342,7 @@ function genXML_init() array("key" => "otros", "def" => "", "req" => false), array("key" => "otrosType", "def" => "", "req" => false), array("key" => "detalles", "def" => "", "req" => true), - array("key" => "informacion_referencia", "def" => "", "req" => true), + array("key" => "informacion_referencia", "def" => "", "req" => false), array("key" => "otrosCargos", "def" => "", "req" => false) ), 'file' => 'genXML.php' @@ -415,7 +415,7 @@ function genXML_init() array("key" => "otros", "def" => "", "req" => false), array("key" => "otrosType", "def" => "", "req" => false), array("key" => "detalles", "def" => "", "req" => true), - array("key" => "informacion_referencia", "def" => "", "req" => true), + array("key" => "informacion_referencia", "def" => "", "req" => false), array("key" => "otrosCargos", "def" => "", "req" => false) ), 'file' => 'genXML.php' @@ -475,7 +475,7 @@ function genXML_init() array("key" => "total_impuestos_asumidos_fabrica", "def" => "", "req" => false), array("key" => "totalOtrosCargos", "def" => "", "req" => false), array("key" => "total_comprobante", "def" => "", "req" => true), - array("key" => "informacion_referencia", "def" => "", "req" => true), + array("key" => "informacion_referencia", "def" => "", "req" => false), array("key" => "otros", "def" => "", "req" => false) ), 'file' => 'genXML.php' diff --git a/api/core/tools.php b/api/core/tools.php index 8a273be0..166be5d7 100644 --- a/api/core/tools.php +++ b/api/core/tools.php @@ -18,40 +18,41 @@ function tools_reply($response, $killMe = false) { + $httpStatus = 200; switch ($response) { case ERROR_USERS_NO_VALID: - http_response_code(400); + $httpStatus = 400; $response = "Usuario no válido"; $killMe = true; break; case ERROR_USERS_WRONG_LOGIN_INFO: - http_response_code(401); + $httpStatus = 401; $response = "Información de acceso incorrecta"; $killMe = true; break; case ERROR_USERS_NO_VALID_SESSION: - http_response_code(440); + $httpStatus = 440; $response = "Sesión no válida o expirada"; $killMe = true; break; case ERROR_USERS_ACCESS_DENIED: - http_response_code(403); + $httpStatus = 403; $response = "Acceso denegado"; $killMe = true; break; case ERROR_USERS_EXISTS: - http_response_code(409); + $httpStatus = 409; $response = "El usuario ya existe"; $killMe = true; break; default: - http_response_code(200); + $httpStatus = 200; } if (is_array($response) && isset($response['Status'])) { // propagar el codigo de estado HTTP cuando se recibe en el error if (is_numeric($response['Status'])) { - http_response_code($response['Status']); + $httpStatus = $response['Status']; $killMe = $response['Status'] >= 299; if(is_array($response['text']) && count($response['text']) > 1) { // cuando la respuesta es un array de Hacienda, el primer elemento es el código de estado @@ -61,50 +62,48 @@ function tools_reply($response, $killMe = false) } else { switch ($response['Status']) { case 'error': - http_response_code(500); + $httpStatus = 500; $killMe = true; break; case 'ok': - http_response_code(200); + $httpStatus = 200; break; default: - http_response_code(400); + $httpStatus = 400; $killMe = true; } } $response = $response['text']; } - + if ($killMe) { if (is_string($response)) { $response = "ERROR: " . $response; } } else { - http_response_code(200); + $httpStatus = 200; } - if (params_get('replyType', 'json') == 'json') { - _tools_reply(tools_returnJson(array( + if (conf_get('mode', 'core', 'web') == 'cli') { + throw new Exception(json_encode(array( 'status' => ($killMe ? 'error' : 'ok'), - 'resp' => $response + 'resp' => $response, + 'httpStatus' => $httpStatus ))); - - # There will be other reply types soon... - //} - //elseif(params_get('replyType', 'json') == 'plain') - //{ - # Reply all other replyTypes - } else { - if (conf_get('mode', 'core', 'web') == 'cli') - $response .= "\n"; - - _tools_reply($response); } + + _tools_reply(tools_returnJson(array( + 'status' => ($killMe ? 'error' : 'ok'), + 'resp' => $response, + 'httpStatus' => $httpStatus + ))); + } function _tools_reply($response) { + print $response; # This really should not be here, but so far, it should do @@ -120,9 +119,11 @@ function _tools_reply($response) function tools_returnJson($response, $addHeaders = true) { if ($addHeaders && conf_get('mode', 'core', 'web') != 'cli') { + http_response_code($response['httpStatus']); header('Content-Type: text/html; charset=utf-8'); header('Content-Type: application/json'); } + return json_encode($response); } From aa8e7ee2733d1c98ad555c1d4cc3d9f39e086169 Mon Sep 17 00:00:00 2001 From: David Obando Date: Thu, 4 Sep 2025 19:28:32 -0400 Subject: [PATCH 12/30] ultimos cambios, manejo de errores y calculo automatico de totales --- api/contrib/clave/clave.php | 1 - api/contrib/genXML/genXML.php | 977 ++-------------------------- www/xsd/TiqueteElectronico_V4.4.xsd | 1 - 3 files changed, 55 insertions(+), 924 deletions(-) diff --git a/api/contrib/clave/clave.php b/api/contrib/clave/clave.php index ec41b1b7..3a1daa58 100644 --- a/api/contrib/clave/clave.php +++ b/api/contrib/clave/clave.php @@ -44,7 +44,6 @@ */ function getClave($tipoDocumento = "", $tipoCedula = "", $cedula = "", $situacion = "", $codigoPais = "", $consecutivo = "", $codigoSeguridad = "") { - $tipoDocumento = params_get('tipoDocumento'); $tipoCedula = params_get('tipoCedula'); $cedula = params_get('cedula'); $situacion = params_get('situacion'); diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 38511ffc..bc3077fd 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -19,8 +19,10 @@ /* * ************************************************** */ /* Constantes de validacion */ /* * ************************************************** */ +define("DECIMALES", 5); define("TIPODOCREFVALUES", array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '99')); define('CODIDOREFVALUES', array('01', '02', '04', '05', '06', '07', '08', '09', '10', '11', '12', '99')); + const CODIGOACTIVIDADSIZE = 6; const EMISORNOMBREMAXSIZE = 100; const EMISORNUMEROTELMIN = 8; @@ -30,27 +32,24 @@ const RECEPTOROTRASSENASEXTRANJEROMAXSIZE = 300; const EMAIL_REGEX = "/^\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*$/"; -tools_useTool('ValidadorXML.php'); - const AUTO_CALCULAR = ["totalServGravados", "totalServExentos", "totalServExonerado", "totalServNoSujeto", "totalMercanciasGravadas", "totalMercanciasExentas", "totalMercExonerada", "totalMercNoSujeta", "totalGravado", "totalExento", "totalExonerado", "totalNoSujeto", "totalVenta", "totalDescuentos", "totalVentaNeta","totalDesgloseImpuesto", "totalImpuesto"]; -define("DECIMALES", 5); +tools_useTool('ValidadorXML.php'); /** * Devuelve true si todas las claves existen en el array - */ -function array_all(array $keys, array $array) { - foreach ($keys as $key) { - if (!array_key_exists($key, $array)) { - return false; + * + function array_all(array $keys, array $array) { + foreach ($keys as $key) { + if (!array_key_exists($key, $array)) { + return false; + } } + return true; } - return true; -} -/** * Devuelve true si al menos una de las claves existe en el array - */ + * function array_any(array $keys, array $array) { foreach ($keys as $key) { if (array_key_exists($key, $array)) { @@ -58,7 +57,7 @@ function array_any(array $keys, array $array) { } } return false; -} +} */ /** * Valida las reglas conocidas de una linea del detalle para evitar rechazos @@ -146,12 +145,21 @@ function limpiarTexto($texto, $largoMaximo=FALSE) { /* Funcion para generar XML */ /* * ************************************************** */ -function genXMLFe() +function genXMLFe() { + return genXMLGenerico('FE'); +} + +function genXMLTe() { + return genXMLGenerico('TE'); +} + +function genXMLGenerico($tipoDocumento = 'FE') { // Datos contribuyente $clave = params_get("clave"); $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json + $codigoActividadEmisor = params_get("codigo_actividad_emisor"); + // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json $codigoActividadReceptor = params_get("codigo_actividad_receptor"); $consecutivo = params_get("consecutivo"); $fechaEmision = params_get("fecha_emision"); @@ -168,11 +176,11 @@ function genXMLFe() $emisorOtrasSenas = params_get("emisor_otras_senas"); $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); // This API only supports one email address for emisor + // This API only supports one email address for emisor + $emisorEmail = params_get("emisor_email"); $registroFiscal8707 = params_get("registrofiscal8707"); // Datos receptor - // $omitir_receptor = params_get("omitir_receptor"); // Deprecated $receptorNombre = params_get("receptor_nombre"); $receptorTipoIdentif = params_get("receptor_tipo_identif"); $receptorNumIdentif = params_get("receptor_num_identif"); @@ -194,27 +202,7 @@ function genXMLFe() $plazoCredito = params_get("plazo_credito"); $codMoneda = params_get("cod_moneda"); $tipoCambio = params_get("tipo_cambio"); - - /* - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerado = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravado = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalExonerado = params_get("total_exonerado"); - $totalNoSujeto = params_get("total_no_sujeto"); - $totalVenta = params_get("total_ventas"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalComprobante = params_get("total_comprobante"); - */ - - // $totalDescuentos = params_get("total_descuentos"); + $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); $totalIVADevuelto = params_get("totalIVADevuelto"); $totalOtrosCargos = params_get("totalOtrosCargos"); @@ -288,16 +276,30 @@ function genXMLFe() } } - $xmlString = ' - - ' . $clave . ' + + + switch ($tipoDocumento) { + case 'FE': + $xmlString = ' + '; + break; + case 'TE': + $xmlString = ' + '; + break; + } + + $xmlString .= '' . $clave . ' ' . $proveedorSistemas . ' ' . $codigoActividadEmisor . ''; - if (isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { + if ($tipoDocumento == "FE" && isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); @@ -1235,16 +1237,20 @@ function genXMLFe() // // - $xmlString .= ' - '; + switch ($tipoDocumento) { + case 'FE': + $xmlString .= ''; + break; + case 'TE': + $xmlString .= ''; + break; + } // eliminar espacios en blanco para reducir el tamaño del XML $xmlString = preg_replace('/>\s+<', $xmlString); $validacion = ValidadorXML::validateXml($xmlString, $consecutivo); - - if($validacion && $validacion->status == "error") { if(conf_get('mode', 'core', 'web') == 'cli') { return tools_reply([ @@ -1271,7 +1277,6 @@ function genXMLFe() ); } - function genXMLNC() { @@ -3053,878 +3058,6 @@ function genXMLND() ); } -function genXMLTE() -{ - - // Datos contribuyente - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - // Datos emisor - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - // Datos receptor - $omitir_receptor = params_get("omitir_receptor"); // Deprecated - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorProvincia = params_get("receptor_provincia"); - $receptorCanton = params_get("receptor_canton"); - $receptorDistrito = params_get("receptor_distrito"); - $receptorBarrio = params_get("receptor_barrio"); - $receptorOtrasSenas = params_get("receptor_otras_senas"); - $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - // Detalles de tiquete / Factura - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerado = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravado = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalExonerado = params_get("total_exonerado"); - $totalNoSujeto = params_get("total_no_sujeto"); - $totalVenta = params_get("total_ventas"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - $totalIVADevuelto = params_get("totalIVADevuelto"); - $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); - - $otros = json_decode(params_get('otros')); - - // Detalles de la compra - $detalles = json_decode(params_get("detalles")); - $informacionReferencia = json_decode(params_get("informacion_referencia")); - - $otrosCargos = json_decode(params_get("otrosCargos")); - $mediosPago = json_decode(params_get("medios_pago")); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - grace_debug(params_get("detalles")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($mediosPago) && $mediosPago != "") { - grace_debug(params_get("medios_pago")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); - } - - if (isset($otrosCargos) && $otrosCargos != "") { - if (count($otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); - //Delimita el array a solo 15 elementos - $otrosCargos = array_slice($otrosCargos, 0, 15); - } - } - - if (isset($mediosPago) && $mediosPago != "") { - if (count($mediosPago) > 4) { - error_log("medios_pago: " . count($mediosPago) . " is greater than 4"); - //Delimita el array a solo 4 elementos - $mediosPago = array_slice($mediosPago, 0, 4); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); - } - - if ($omitir_receptor != 'true') { - $xmlString .= ' - ' . $receptorNombre . ''; - - if ($receptorTipoIdentif != '' && $receptorNumIdentif != '') { - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - } - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= ' - ' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' - . $receptorOtrasSenasExtranjero . - ''; - } - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - } - - $xmlString .= ''; - } - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ''; - - // cant - unidad medida - detalle - precio unitario - monto total - subtotal - monto total linea - Monto desc -Naturaleza Desc - Impuesto : Codigo / Tarifa / Monto - - /* EJEMPLO DE DETALLES - { - "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], - "2":["1","Sp","Honorarios","100000","100000","100000","100000"] - } - */ - - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - - ' . $l . ''; - - $xmlString .= ' - ' . $d->codigoCABYS . ''; - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array)$d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array)$codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { - $xmlString .= ''; - $lineas = array_slice($d->detalleSurtido, 0, 20); - foreach ($lineas as $linea) { - $xmlString .= ''; - $xmlString .= '' . $linea->codigoCABYSSurtido . ''; - if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { - $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); - foreach ($codigos as $codigo) { - $xmlString .= ''; - $xmlString .= '' . $codigo->tipoSurtido . ''; - $xmlString .= '' . $codigo->codigoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->cantidadSurtido . ''; - $xmlString .= '' . $linea->unidadMedidaSurtido . ''; - if (isset($linea->unidadMedidaComercialSurtido)) { - $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; - } - $xmlString .= '' . $linea->detalleSurtido . ''; - $xmlString .= '' . $linea->precioUnitarioSurtido . ''; - $xmlString .= '' . $linea->montoTotalSurtido . ''; - if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { - $descuentos = array_slice($linea->descuentoSurtido, 0, 5); - foreach ($descuentos as $desc) { - $xmlString .= ''; - $xmlString .= '' . $desc->montoDescuentoSurtido . ''; - $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; - if (isset($desc->descuentoSurtidoOtros)) { - $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; - } - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->subTotalSurtido . ''; - if (isset($linea->ivaCobradoFabricaSurtido)) { - $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; - } - $xmlString .= '' . $linea->baseImponibleSurtido . ''; - if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { - $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); - foreach ($impuestos as $imp) { - $xmlString .= ''; - $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; - if (isset($imp->codigoImpuestoOTROSurtido)) { - $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; - } - if (isset($imp->codigoTarifaIVASurtido)) { - $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; - } - if (isset($imp->tarifaSurtido)) { - $xmlString .= '' . $imp->tarifaSurtido . ''; - } - if (isset($imp->datosImpuestoEspecificoSurtido)) { - $e = $imp->datosImpuestoEspecificoSurtido; - $xmlString .= ''; - if (isset($e->cantidadUnidadMedidaSurtido)) { - $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; - } - if (isset($e->porcentajeSurtido)) { - $xmlString .= '' . $e->porcentajeSurtido . ''; - } - if (isset($e->proporcionSurtido)) { - $xmlString .= '' . $e->proporcionSurtido . ''; - } - if (isset($e->volumenUnidadConsumoSurtido)) { - $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; - } - if (isset($e->impuestoUnidadSurtido)) { - $xmlString .= '' . $e->impuestoUnidadSurtido . ''; - } - $xmlString .= ''; - } - $xmlString .= '' . $imp->montoImpuestoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= ''; - } - $xmlString .= ''; - } - - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array)$d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array)$descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . $c['montoDescuento'] . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { - $xmlString .= '' . $d->IVACobradoFabrica . ''; - } - - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - - ' . $i->codigo . ''; - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= ' - ' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= ' - ' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= ' - ' . $i->factorIVA . ''; - } - - if ( - isset($i->codigo) && - in_array($i->codigo, ["03", "04", "05", "06"]) && - isset($i->datosImpuestoEspecifico) && - is_object($i->datosImpuestoEspecifico) - ) { - $datosImpuestoEsp = $i->datosImpuestoEspecifico; - $xmlString .= ''; - if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { - $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; - } - if (isset($datosImpuestoEsp->porcentaje)) { - $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; - } - if (isset($datosImpuestoEsp->proporcion)) { - $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; - } - if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { - $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; - } - if (isset($datosImpuestoEsp->impuestoUnidad)) { - $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; - } - $xmlString .= ''; - } - - $xmlString .= ' - ' . $i->monto . ''; - - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' - - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; - } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; - } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; - } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; - } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' - '; - } - - $xmlString .= ' - '; - } - } - $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; - $xmlString .= '' . $d->impuestoNeto . ''; - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerado != '') { - $xmlString .= ' - ' . $totalServExonerado . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if ($totalIVADevuelto != '') { - $xmlString .= ' - ' . $totalIVADevuelto . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} - function genXMLMr() { diff --git a/www/xsd/TiqueteElectronico_V4.4.xsd b/www/xsd/TiqueteElectronico_V4.4.xsd index 6743e886..03e767c3 100644 --- a/www/xsd/TiqueteElectronico_V4.4.xsd +++ b/www/xsd/TiqueteElectronico_V4.4.xsd @@ -929,7 +929,6 @@ se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. - From 613b71c11c7755b545f75596df49512d733a5545 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 5 Sep 2025 03:53:17 -0400 Subject: [PATCH 13/30] fix problema en la validacion de la cedula en el certificado --- api/contrib/signXML/Firmadohaciendacr.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/contrib/signXML/Firmadohaciendacr.php b/api/contrib/signXML/Firmadohaciendacr.php index a420e2f3..8e0a2f03 100644 --- a/api/contrib/signXML/Firmadohaciendacr.php +++ b/api/contrib/signXML/Firmadohaciendacr.php @@ -95,7 +95,7 @@ public function firmar($certificadop12, $clavecertificado, $xmlsinfirma, $tipodo $this->Modulus = base64_encode($complem['rsa']['n']); $this->Exponent = base64_encode($complem['rsa']['e']); } else { - echo "Error: No se puede leer el almacén de certificados o la clave no es la correcta.\n"; + echo "Error: No se puede leer el almacén de certificados o la clave no es la correcta [$clavecertificado].\n"; exit; } @@ -175,7 +175,7 @@ public function insertaFirma($xml) $clave = substr($xml, $startPos+7, 50); $issuerCedula = $certData['subject']['serialNumber']; - $issuerCedula = preg_replace('/^CPF/', '', $issuerCedula); + $issuerCedula = preg_replace('/^(CPF|CPJ)/', '', $issuerCedula); $issuerCedula = str_replace('-', '', $issuerCedula); $issuerCedula = ltrim($issuerCedula, '0'); if(strpos($clave, $issuerCedula) === false){ From 051b58651411668a4578337f39f19ec9161c37ba Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 5 Sep 2025 04:09:00 -0400 Subject: [PATCH 14/30] ajustar la revision de sumatoria de metodo de pago a 0 decimales --- api/contrib/genXML/genXML.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index bc3077fd..49b3139b 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -1115,7 +1115,7 @@ function genXMLGenerico($tipoDocumento = 'FE') $totalMediosPago += floatval($o->totalMedioPago); } - if (round((float)$totalMediosPago, 2) !== round((float)$calculados['totalComprobante'], 2)) { + if (round((float)$totalMediosPago,0) !== round((float)$calculados['totalComprobante'], 0)) { tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [" . (float)$totalMediosPago . "], Total Comprobante: [" . (float)$calculados['totalComprobante'] ."] XML: $xmlString", true); } } From 223b51c7768b8b7d3b2437a882e17d08b944f053 Mon Sep 17 00:00:00 2001 From: David Obando Date: Sat, 6 Sep 2025 15:40:09 -0400 Subject: [PATCH 15/30] fix: redondear valores --- api/contrib/genXML/genXML.php | 8 ++++---- api/tools/ValidadorXML.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 49b3139b..f96de256 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -735,7 +735,7 @@ function genXMLGenerico($tipoDocumento = 'FE') isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" ) { - $c['montoDescuento'] = floatval($c['montoDescuento']) * $d->cantidad; + $c['montoDescuento'] = floatval($c['montoDescuento'], DECIMALES) * $d->cantidad; $xmlStringDescuento = ' ' . $c['montoDescuento'] . ' ' . $c['codigoDescuento'] . ''; @@ -763,7 +763,7 @@ function genXMLGenerico($tipoDocumento = 'FE') // Calcular los valores que se pueden derivar de los datos ingresados autoCalcularLinea($l, $d, $totalDescuentos, $calculados); - $xmlString .= '' . $d->precioUnitario . ''; + $xmlString .= '' . round(floatval($d->precioUnitario),DECIMALES) . ''; $xmlString .= '' . $calculados[$l]['montoTotal'] . ''; $xmlString .= $xmlStringDescuento; $xmlString .= '' . $calculados[$l]['subTotal'] . ''; @@ -1705,7 +1705,7 @@ function genXMLNC() } $xmlString .= ' - ' . $d->precioUnitario . ' + ' . round(floatval($d->precioUnitario),DECIMALES) . ' ' . $d->montoTotal . ''; if (isset($d->descuento) && !empty($d->descuento)) { @@ -1725,7 +1725,7 @@ function genXMLNC() ) { $xmlString .= ' - ' . $c['montoDescuento'] . ' + ' . round(floatval($c['montoDescuento']),DECIMALES) . ' ' . $c['codigoDescuento'] . ''; // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo if ( diff --git a/api/tools/ValidadorXML.php b/api/tools/ValidadorXML.php index 885d886c..96673df1 100644 --- a/api/tools/ValidadorXML.php +++ b/api/tools/ValidadorXML.php @@ -44,7 +44,7 @@ public static function validateXml($contenido_xml, $consecutivo, $version = "4.4 { $tipo_doc = substr($consecutivo, 8, 2); $baseFolder = conf_get('coreInstall', 'modules', ''); - $baseFolder = str_replace('/api', '/www', $baseFolder); + $baseFolder = preg_replace('/\/api\/$/', '/www/', $baseFolder); $schemas = [ "01" => "$baseFolder/xsd/FacturaElectronica_V$version.xsd", "02" => "$baseFolder/xsd/FacturaCompra_V$version.xsd", From c278f0c9cc3452e91b0fb50b12a51faae5c1f54e Mon Sep 17 00:00:00 2001 From: David Obando Date: Sat, 6 Sep 2025 15:44:49 -0400 Subject: [PATCH 16/30] fix en genXML redondear valores al maximo permitido --- api/contrib/genXML/genXML.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index f96de256..60b91af6 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -735,7 +735,7 @@ function genXMLGenerico($tipoDocumento = 'FE') isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" ) { - $c['montoDescuento'] = floatval($c['montoDescuento'], DECIMALES) * $d->cantidad; + $c['montoDescuento'] = round(floatval($c['montoDescuento']), DECIMALES) * $d->cantidad; $xmlStringDescuento = ' ' . $c['montoDescuento'] . ' ' . $c['codigoDescuento'] . ''; From 4431a91fdf039b84973691f56d676a0db7374246 Mon Sep 17 00:00:00 2001 From: David Obando Date: Sat, 6 Sep 2025 17:28:32 -0400 Subject: [PATCH 17/30] remover id en tiquetes si no se brinda ambos datos bien --- api/contrib/genXML/genXML.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 60b91af6..9c065d69 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -359,11 +359,13 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ' ' . $receptorNombre . ''; - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; + if(!empty($receptorTipoIdentif) && !empty($receptorNumIdentif)) { + $xmlString .= ' + + ' . $receptorTipoIdentif . ' + ' . $receptorNumIdentif . ' + '; + } if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { $xmlString .= ' From 38163563bcd363ad4c1de02c5ad4f1a04c093bd7 Mon Sep 17 00:00:00 2001 From: David Obando Date: Tue, 9 Sep 2025 00:21:21 -0400 Subject: [PATCH 18/30] fix para usar el tipo de cambio que se envia --- api/contrib/genXML/genXML.php | 210 +++++++++++++++++----------------- 1 file changed, 103 insertions(+), 107 deletions(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 9c065d69..b3c010ed 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -32,7 +32,7 @@ const RECEPTOROTRASSENASEXTRANJEROMAXSIZE = 300; const EMAIL_REGEX = "/^\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*$/"; -const AUTO_CALCULAR = ["totalServGravados", "totalServExentos", "totalServExonerado", "totalServNoSujeto", "totalMercanciasGravadas", "totalMercanciasExentas", "totalMercExonerada", "totalMercNoSujeta", "totalGravado", "totalExento", "totalExonerado", "totalNoSujeto", "totalVenta", "totalDescuentos", "totalVentaNeta","totalDesgloseImpuesto", "totalImpuesto"]; +const AUTO_CALCULAR = ["totalServGravados", "totalServExentos", "totalServExonerado", "totalServNoSujeto", "totalMercanciasGravadas", "totalMercanciasExentas", "totalMercExonerada", "totalMercNoSujeta", "totalGravado", "totalExento", "totalExonerado", "totalNoSujeto", "totalVenta", "totalDescuentos", "totalVentaNeta", "totalDesgloseImpuesto", "totalImpuesto"]; tools_useTool('ValidadorXML.php'); @@ -66,10 +66,11 @@ function array_any(array $keys, array $array) { * @param object $detallesLinea * @return void */ -function validarLinea($numLinea, $d) { - $d = (array)$d; +function validarLinea($numLinea, $d) +{ + $d = (array) $d; - if(isset($d['baseImponible']) && !isset($d['impuesto'])) { + if (isset($d['baseImponible']) && !isset($d['impuesto'])) { tools_reply([ "Status" => 400, "text" => "Detalle de líneas $numLinea: cuando se envía el campo 'baseImponible' es obligatorio enviar también el campo 'impuesto'", @@ -85,21 +86,22 @@ function validarLinea($numLinea, $d) { * @param array &$calculados * @return void */ -function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) { +function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) +{ - if(!isset($calculados['totalDescuentos'])) { + if (!isset($calculados['totalDescuentos'])) { $calculados['totalDescuentos'] = 0; } $calculados['totalDescuentos'] += round($totalDescuentos, DECIMALES); - if(!isset($calculados[$numLinea])) { + if (!isset($calculados[$numLinea])) { $calculados[$numLinea] = []; } // Se obtiene de la resta del campo monto total menos monto de descuento concedido $calculados[$numLinea]['montoTotal'] = round($d->precioUnitario * $d->cantidad, DECIMALES); $calculados[$numLinea]['subTotal'] = round($calculados[$numLinea]['montoTotal'] - $totalDescuentos, DECIMALES); - + /* @TODO base imponible puede incluir Este campo será de condición obligatoria, cuando el producto/ servicio este gravado con algún impuesto. @@ -115,9 +117,9 @@ function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) $calculados[$numLinea]['baseImponible'] = $calculados[$numLinea]['subTotal']; - foreach(AUTO_CALCULAR as $field) { - if(!isset($calculados[$field])) { - if($field !== 'totalDesgloseImpuesto') { + foreach (AUTO_CALCULAR as $field) { + if (!isset($calculados[$field])) { + if ($field !== 'totalDesgloseImpuesto') { $calculados[$field] = 0; continue; } @@ -131,10 +133,11 @@ function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) * Elimina caracteres no permitidos * Solo letras, numeros, espacios y signos de puntuacion comunes en detalle */ -function limpiarTexto($texto, $largoMaximo=FALSE) { +function limpiarTexto($texto, $largoMaximo = FALSE) +{ $limpio = preg_replace('/[^\\p{L}\\p{N}\\s.,;:\\-\\_\\(\\)\\[\\]\\{\\}\\!\\?\'"\\n\\r]/u', '', $texto); - - if($largoMaximo === FALSE) { + + if ($largoMaximo === FALSE) { return $limpio; } // Trunca a la longitud máxima permitida @@ -145,11 +148,13 @@ function limpiarTexto($texto, $largoMaximo=FALSE) { /* Funcion para generar XML */ /* * ************************************************** */ -function genXMLFe() { +function genXMLFe() +{ return genXMLGenerico('FE'); } -function genXMLTe() { +function genXMLTe() +{ return genXMLGenerico('TE'); } @@ -202,11 +207,11 @@ function genXMLGenerico($tipoDocumento = 'FE') $plazoCredito = params_get("plazo_credito"); $codMoneda = params_get("cod_moneda"); $tipoCambio = params_get("tipo_cambio"); - + $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); $totalIVADevuelto = params_get("totalIVADevuelto"); $totalOtrosCargos = params_get("totalOtrosCargos"); - + $otros = json_decode(params_get('otros')); // Detalles de la compra @@ -276,7 +281,7 @@ function genXMLGenerico($tipoDocumento = 'FE') } } - + switch ($tipoDocumento) { case 'FE': @@ -328,7 +333,7 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ' ' . $emisorNombreComercial . ''; } - + if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito) { $xmlString .= ' ' . $emisorProv . ' @@ -338,7 +343,7 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= '' . $emisorBarrio . ''; } $xmlString .= '123456'; - + $xmlString .= ''; } @@ -359,7 +364,7 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ' ' . $receptorNombre . ''; - if(!empty($receptorTipoIdentif) && !empty($receptorNumIdentif)) { + if (!empty($receptorTipoIdentif) && !empty($receptorNumIdentif)) { $xmlString .= ' ' . $receptorTipoIdentif . ' @@ -427,7 +432,7 @@ function genXMLGenerico($tipoDocumento = 'FE') ' . $plazoCredito . ''; } else { // es credito segun la condicion de venta - if($condVenta == "02") { + if ($condVenta == "02") { tools_reply([ "Status" => 400, "text" => "El campo 'plazoCredito' es obligatorio cuando 'condicionVenta' es '02'" @@ -587,7 +592,7 @@ function genXMLGenerico($tipoDocumento = 'FE') if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array)$d->codigoComercial; + $codigoComercialArray = (array) $d->codigoComercial; // Delimitar el array a solo 5 elementos if (count($codigoComercialArray) > 5) { @@ -597,7 +602,7 @@ function genXMLGenerico($tipoDocumento = 'FE') // Iterar sobre los elementos del array foreach ($codigoComercialArray as $codigos) { - $c = (array)$codigos; + $c = (array) $codigos; // Verificar si el elemento es un array asociativo if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { $xmlString .= ' @@ -622,7 +627,7 @@ function genXMLGenerico($tipoDocumento = 'FE') } $d->detalle = limpiarTexto($d->detalle, 150); - + $xmlString .= '' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { $xmlString .= '' . $d->numeroVINoSerie . ''; @@ -722,8 +727,8 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlStringDescuento = ''; // Procesar descuentos, si existen if (isset($d->descuento) && !empty($d->descuento)) { - - $descuentoArray = (array)$d->descuento; + + $descuentoArray = (array) $d->descuento; if (count($descuentoArray) > 5) { error_log("descuento: " . count($descuentoArray) . " is greater than 5"); @@ -731,7 +736,7 @@ function genXMLGenerico($tipoDocumento = 'FE') $descuentoArray = array_slice($descuentoArray, 0, 5); foreach ($descuentoArray as $descuentos) { - $c = (array)$descuentos; + $c = (array) $descuentos; if ( is_array($c) && isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && @@ -764,8 +769,8 @@ function genXMLGenerico($tipoDocumento = 'FE') // Calcular los valores que se pueden derivar de los datos ingresados autoCalcularLinea($l, $d, $totalDescuentos, $calculados); - - $xmlString .= '' . round(floatval($d->precioUnitario),DECIMALES) . ''; + + $xmlString .= '' . round(floatval($d->precioUnitario), DECIMALES) . ''; $xmlString .= '' . $calculados[$l]['montoTotal'] . ''; $xmlString .= $xmlStringDescuento; $xmlString .= '' . $calculados[$l]['subTotal'] . ''; @@ -775,7 +780,7 @@ function genXMLGenerico($tipoDocumento = 'FE') } $xmlString .= '' . $calculados[$l]['baseImponible'] . ''; - + if (isset($d->impuesto) && $d->impuesto != "") { foreach ($d->impuesto as $i) { $xmlString .= ' @@ -791,7 +796,7 @@ function genXMLGenerico($tipoDocumento = 'FE') } // calcular el monto de impuesto si no se proporciona usando el codigo y la tarifa - if($i->codigo==="01" && isset($i->codigoTarifa)) { + if ($i->codigo === "01" && isset($i->codigoTarifa)) { $impuestoPorTarifa = [ "01" => 0, // 0% (Artículo 32, num 1, RLIVA) @@ -845,11 +850,11 @@ function genXMLGenerico($tipoDocumento = 'FE') } $xmlString .= ''; } - + if (!isset($i->monto)) { $i->{"monto"} = $calculados[$l]['monto']; } else { - if($i->monto != $calculados[$l]['monto']) { + if ($i->monto != $calculados[$l]['monto']) { tools_reply("El monto del impuesto no coincide con el calculado para la línea $l, Calculado: " . $calculados[$l]['monto'], true); } } @@ -883,14 +888,14 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ''; - if(isset($i->codigo) && isset($i->monto)) { + if (isset($i->codigo) && isset($i->monto)) { // Se suma el impuesto neto a totalImpuestos - if(!isset($calculados[$l]['impuestoNeto'])) { + if (!isset($calculados[$l]['impuestoNeto'])) { $calculados[$l]['impuestoNeto'] = 0; } $calculados[$l]['impuestoNeto'] += round($i->monto, DECIMALES); - - if($i->codigoTarifa === "10" || $i->codigoTarifa === "11") { + + if ($i->codigoTarifa === "10" || $i->codigoTarifa === "11") { // Exento $resumenKeys = [ "servicio" => "totalServExentos", @@ -905,11 +910,11 @@ function genXMLGenerico($tipoDocumento = 'FE') ]; $calculados['totalGravado'] += ($calculados[$l]['montoTotal']); } - + // Se suma el impuesto neto a totalImpuestos // 4.4 se basa en los cabys para saber que es un servicio o mercaderia, // los codigos que empiezan de 0 a 4 son mercaderias y los que empiezan de 5 a 9 son servicios - if(isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5','6','7','8','9'], true)) { + if (isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5', '6', '7', '8', '9'], true)) { // Servicio $calculados[$resumenKeys['servicio']] += ($calculados[$l]['montoTotal']); } else { @@ -922,17 +927,17 @@ function genXMLGenerico($tipoDocumento = 'FE') // Desglose de impuestos por código y tarifa $tdiKey = $i->codigo . "-" . $i->codigoTarifa; - if(!isset($calculados['totalDesgloseImpuesto'][$tdiKey])) { + if (!isset($calculados['totalDesgloseImpuesto'][$tdiKey])) { $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ "Codigo" => $i->codigo, - "CodigoTarifaIVA" => $i->codigoTarifa, + "CodigoTarifaIVA" => $i->codigoTarifa, "TotalMontoImpuesto" => $i->monto ]; } else { $_totalMontoImpuesto = $calculados['totalDesgloseImpuesto'][$tdiKey]->TotalMontoImpuesto; $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ "Codigo" => $i->codigo, - "CodigoTarifaIVA" => $i->codigoTarifa, + "CodigoTarifaIVA" => $i->codigoTarifa, "TotalMontoImpuesto" => $i->monto + $_totalMontoImpuesto ]; } @@ -945,9 +950,9 @@ function genXMLGenerico($tipoDocumento = 'FE') } else { $xmlString .= '0'; } - + $xmlString .= '' . $calculados[$l]['impuestoNeto'] . ''; - + // Se calcula de la siguiente manera: se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. $calculados[$l]['montoTotalLinea'] = $calculados[$l]['subTotal'] + $calculados[$l]['impuestoNeto']; @@ -960,7 +965,7 @@ function genXMLGenerico($tipoDocumento = 'FE') } $xmlString .= ''; - + //OtrosCargos if (isset($otrosCargos) && $otrosCargos != "") { foreach ($otrosCargos as $o) { @@ -997,7 +1002,7 @@ function genXMLGenerico($tipoDocumento = 'FE') } foreach ($calculados as $key => $value) { - if(is_numeric($value)) { + if (is_numeric($value)) { $calculados[$key] = round($value, DECIMALES); } } @@ -1005,19 +1010,10 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ' '; - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - + $xmlString .= ' ' . $codMoneda . ' ' . $tipoCambio . ' '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } // Se obtiene de la suma de los campos "Total servicios No Sujetos de IVA" mas "Total mercancías No Sujetas de IVA". // @TODO revisar no sujetos @@ -1026,15 +1022,15 @@ function genXMLGenerico($tipoDocumento = 'FE') // Se obtiene de la suma de los campos "total servicios exonerados de IVA" mas "total de mercancías exoneradas del IVA". // @TODO revisar exoneraciones // $calculados['totalExonerado'] = $calculados['totalServExonerados'] + $calculados['totalMercanciasExoneradas']; - + // Calcular totalVenta // Se obtiene de la sumatoria de los campos “total gravado”, “total exento”, “Total Exonerado” y “Total No Sujeto - $calculados['totalVenta'] = - $calculados['totalGravado'] + - $calculados['totalExento'] + - $calculados['totalExonerado'] + + $calculados['totalVenta'] = + $calculados['totalGravado'] + + $calculados['totalExento'] + + $calculados['totalExonerado'] + $calculados['totalNoSujeto']; - + // Calcular totalVentaNeta // Se obtiene de la resta de los campos total venta menos total descuento $calculados['totalVentaNeta'] = $calculados['totalVenta'] - $calculados['totalDescuentos']; @@ -1050,9 +1046,9 @@ function genXMLGenerico($tipoDocumento = 'FE') // agrega los campos del resumen que esten en el array calculados y no esten vacios foreach (AUTO_CALCULAR as $campoResumen) { - if($campoResumen == "totalDesgloseImpuesto") { + if ($campoResumen == "totalDesgloseImpuesto") { // Add logic for TotalDesgloseImpuesto - $totalDesgloseImpuesto = $calculados['totalDesgloseImpuesto']?? []; + $totalDesgloseImpuesto = $calculados['totalDesgloseImpuesto'] ?? []; if (count($totalDesgloseImpuesto) > 0) { foreach ($totalDesgloseImpuesto as $impuesto) { $xmlString .= ' @@ -1072,11 +1068,11 @@ function genXMLGenerico($tipoDocumento = 'FE') continue; } - if($calculados[$campoResumen]!='') { + if ($calculados[$campoResumen] != '') { $xmlString .= '<' . ucfirst($campoResumen) . '>' . $calculados[$campoResumen] . ''; } } - + if ($totalImpAsumidoEmisorFabrica != '') { $xmlString .= ' ' . $totalImpAsumidoEmisorFabrica . ''; @@ -1116,9 +1112,9 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ''; $totalMediosPago += floatval($o->totalMedioPago); } - - if (round((float)$totalMediosPago,0) !== round((float)$calculados['totalComprobante'], 0)) { - tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [" . (float)$totalMediosPago . "], Total Comprobante: [" . (float)$calculados['totalComprobante'] ."] XML: $xmlString", true); + + if (round((float) $totalMediosPago, 0) !== round((float) $calculados['totalComprobante'], 0)) { + tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [" . (float) $totalMediosPago . "], Total Comprobante: [" . (float) $calculados['totalComprobante'] . "] XML: $xmlString", true); } } @@ -1253,29 +1249,29 @@ function genXMLGenerico($tipoDocumento = 'FE') $validacion = ValidadorXML::validateXml($xmlString, $consecutivo); - if($validacion && $validacion->status == "error") { - if(conf_get('mode', 'core', 'web') == 'cli') { + if ($validacion && $validacion->status == "error") { + if (conf_get('mode', 'core', 'web') == 'cli') { return tools_reply([ "Status" => 400, "text" => array( - "clave" => $clave, - "validacion" => $validacion - ) + "clave" => $clave, + "validacion" => $validacion + ) ], true); } tools_reply([ "Status" => 400, "text" => array( - "clave" => $clave, - "validacion" => $validacion - ) + "clave" => $clave, + "validacion" => $validacion + ) ], true); } return array( "clave" => $clave, - "xml" => base64_encode($xmlString) + "xml" => base64_encode($xmlString) ); } @@ -1472,9 +1468,9 @@ function genXMLNC() $xmlString .= '' . trim($emisorEmail) . ''; } else { return tools_reply([ - "Status" => 400, - "text" => "El correo del emisor no cumple con el formato establecido." - ], true); + "Status" => 400, + "text" => "El correo del emisor no cumple con el formato establecido." + ], true); } if ($omitir_receptor != 'true') { @@ -1576,7 +1572,7 @@ function genXMLNC() if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array)$d->codigoComercial; + $codigoComercialArray = (array) $d->codigoComercial; // Delimitar el array a solo 5 elementos if (count($codigoComercialArray) > 5) { @@ -1586,7 +1582,7 @@ function genXMLNC() // Iterar sobre los elementos del array foreach ($codigoComercialArray as $codigos) { - $c = (array)$codigos; + $c = (array) $codigos; // Verificar si el elemento es un array asociativo if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { $xmlString .= ' @@ -1707,11 +1703,11 @@ function genXMLNC() } $xmlString .= ' - ' . round(floatval($d->precioUnitario),DECIMALES) . ' + ' . round(floatval($d->precioUnitario), DECIMALES) . ' ' . $d->montoTotal . ''; if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array)$d->descuento; + $descuentoArray = (array) $d->descuento; if (count($descuentoArray) > 5) { error_log("descuento: " . count($descuentoArray) . " is greater than 5"); @@ -1719,7 +1715,7 @@ function genXMLNC() $descuentoArray = array_slice($descuentoArray, 0, 5); foreach ($descuentoArray as $descuentos) { - $c = (array)$descuentos; + $c = (array) $descuentos; if ( is_array($c) && isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && @@ -1727,7 +1723,7 @@ function genXMLNC() ) { $xmlString .= ' - ' . round(floatval($c['montoDescuento']),DECIMALES) . ' + ' . round(floatval($c['montoDescuento']), DECIMALES) . ' ' . $c['codigoDescuento'] . ''; // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo if ( @@ -2168,7 +2164,7 @@ function genXMLNC() return array( "clave" => $clave, - "xml" => base64_encode($xmlString), + "xml" => base64_encode($xmlString), "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); } @@ -2198,7 +2194,7 @@ function genXMLND() $emisorTel = params_get("emisor_tel"); $emisorEmail = params_get("emisor_email"); $registroFiscal8707 = params_get("registrofiscal8707"); - + // Datos receptor $omitir_receptor = params_get("omitir_receptor"); // Deprecated $receptorNombre = params_get("receptor_nombre"); @@ -2463,7 +2459,7 @@ function genXMLND() if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array)$d->codigoComercial; + $codigoComercialArray = (array) $d->codigoComercial; // Delimitar el array a solo 5 elementos if (count($codigoComercialArray) > 5) { @@ -2473,7 +2469,7 @@ function genXMLND() // Iterar sobre los elementos del array foreach ($codigoComercialArray as $codigos) { - $c = (array)$codigos; + $c = (array) $codigos; // Verificar si el elemento es un array asociativo if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { $xmlString .= ' @@ -2598,7 +2594,7 @@ function genXMLND() ' . $d->montoTotal . ''; if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array)$d->descuento; + $descuentoArray = (array) $d->descuento; if (count($descuentoArray) > 5) { error_log("descuento: " . count($descuentoArray) . " is greater than 5"); @@ -2606,7 +2602,7 @@ function genXMLND() $descuentoArray = array_slice($descuentoArray, 0, 5); foreach ($descuentoArray as $descuentos) { - $c = (array)$descuentos; + $c = (array) $descuentos; if ( is_array($c) && isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && @@ -3055,7 +3051,7 @@ function genXMLND() return array( "clave" => $clave, - "xml" => base64_encode($xmlString), + "xml" => base64_encode($xmlString), "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); } @@ -3247,7 +3243,7 @@ function genXMLFec() $mediosPago = array_slice($mediosPago, 0, 4); } } - + $xmlString = ' codigoComercial) && !empty($d->codigoComercial)) { // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array)$d->codigoComercial; + $codigoComercialArray = (array) $d->codigoComercial; // Delimitar el array a solo 5 elementos if (count($codigoComercialArray) > 5) { @@ -3406,7 +3402,7 @@ function genXMLFec() // Iterar sobre los elementos del array foreach ($codigoComercialArray as $codigos) { - $c = (array)$codigos; + $c = (array) $codigos; // Verificar si el elemento es un array asociativo if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { $xmlString .= ' @@ -3448,7 +3444,7 @@ function genXMLFec() ' . $d->montoTotal . ''; if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array)$d->descuento; + $descuentoArray = (array) $d->descuento; if (count($descuentoArray) > 5) { error_log("descuento: " . count($descuentoArray) . " is greater than 5"); @@ -3456,7 +3452,7 @@ function genXMLFec() $descuentoArray = array_slice($descuentoArray, 0, 5); foreach ($descuentoArray as $descuentos) { - $c = (array)$descuentos; + $c = (array) $descuentos; if ( is_array($c) && isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && @@ -3865,7 +3861,7 @@ function genXMLFec() return array( "clave" => $clave, - "xml" => base64_encode($xmlString), + "xml" => base64_encode($xmlString), "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); } @@ -4113,7 +4109,7 @@ function genXMLFee() if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array)$d->codigoComercial; + $codigoComercialArray = (array) $d->codigoComercial; // Delimitar el array a solo 5 elementos if (count($codigoComercialArray) > 5) { @@ -4123,7 +4119,7 @@ function genXMLFee() // Iterar sobre los elementos del array foreach ($codigoComercialArray as $codigos) { - $c = (array)$codigos; + $c = (array) $codigos; // Verificar si el elemento es un array asociativo if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { $xmlString .= ' @@ -4249,7 +4245,7 @@ function genXMLFee() ' . $d->montoTotal . ''; if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array)$d->descuento; + $descuentoArray = (array) $d->descuento; if (count($descuentoArray) > 5) { error_log("descuento: " . count($descuentoArray) . " is greater than 5"); @@ -4257,7 +4253,7 @@ function genXMLFee() $descuentoArray = array_slice($descuentoArray, 0, 5); foreach ($descuentoArray as $descuentos) { - $c = (array)$descuentos; + $c = (array) $descuentos; if ( is_array($c) && isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && @@ -4711,7 +4707,7 @@ function genXMLFee() return array( "clave" => $clave, - "xml" => base64_encode($xmlString), + "xml" => base64_encode($xmlString), "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) ); } From 88a53e50baeb62bec5be818790edaa3dc2fba541 Mon Sep 17 00:00:00 2001 From: David Obando Date: Tue, 16 Sep 2025 19:11:46 -0400 Subject: [PATCH 19/30] fix ignorar diferencias en pagos de menos de 1 --- api/contrib/genXML/genXML.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index b3c010ed..4fa37b6d 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -1113,8 +1113,10 @@ function genXMLGenerico($tipoDocumento = 'FE') $totalMediosPago += floatval($o->totalMedioPago); } - if (round((float) $totalMediosPago, 0) !== round((float) $calculados['totalComprobante'], 0)) { - tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [" . (float) $totalMediosPago . "], Total Comprobante: [" . (float) $calculados['totalComprobante'] . "] XML: $xmlString", true); + $t1 = round((float) $totalMediosPago, 2); + $t2 = round((float) $calculados['totalComprobante'], 2); + if (abs($t1 - $t2)>1) { + tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [$t1], Total Comprobante: [$t2]", true); } } From 2eb5219ccf7c10639248d9bd4763bf7a29d86f92 Mon Sep 17 00:00:00 2001 From: David Obando Date: Sun, 21 Sep 2025 21:29:41 -0400 Subject: [PATCH 20/30] ultimos cambios, agrega validaciones adicionales --- api/contrib/genXML/genXML.php | 3517 +---------------- api/contrib/genXML/genXML.txt | 4755 +++++++++++++++++++++++ api/contrib/genXML/module.php | 1 + www/xsd/NotaCreditoElectronica_V4.4.xsd | 1 - 4 files changed, 4796 insertions(+), 3478 deletions(-) create mode 100644 api/contrib/genXML/genXML.txt diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 4fa37b6d..ea7bd663 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -158,6 +158,16 @@ function genXMLTe() return genXMLGenerico('TE'); } +function genXMLNc() +{ + return genXMLGenerico('NC'); +} + +function genXMLNd() +{ + return genXMLGenerico('ND'); +} + function genXMLGenerico($tipoDocumento = 'FE') { // Datos contribuyente @@ -281,7 +291,7 @@ function genXMLGenerico($tipoDocumento = 'FE') } } - + $xmlClosingTag = ''; switch ($tipoDocumento) { case 'FE': @@ -290,6 +300,7 @@ function genXMLGenerico($tipoDocumento = 'FE') xmlns="https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/facturaElectronica" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'; + $xmlClosingTag = ''; break; case 'TE': $xmlString = ' @@ -297,7 +308,29 @@ function genXMLGenerico($tipoDocumento = 'FE') xmlns="https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/tiqueteElectronico" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'; + $xmlClosingTag = ''; + break; + case 'NC': + $xmlString = ' + '; + $xmlClosingTag = ''; + break; + case 'ND': + $xmlString = ' + '; + $xmlClosingTag = ''; break; + default: + tools_reply([ + "Status" => 400, + "text" => "El campo 'tipoDocumento' debe ser uno de los siguientes valores: FE, TE, NC, ND" + ], true); } $xmlString .= '' . $clave . ' @@ -1115,7 +1148,7 @@ function genXMLGenerico($tipoDocumento = 'FE') $t1 = round((float) $totalMediosPago, 2); $t2 = round((float) $calculados['totalComprobante'], 2); - if (abs($t1 - $t2)>1) { + if (abs($t1 - $t2) > 1) { tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [$t1], Total Comprobante: [$t2]", true); } } @@ -1124,6 +1157,7 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ' '; + // Información Referencia if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { foreach ($informacionReferencia as $ref) { if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { @@ -1154,36 +1188,6 @@ function genXMLGenerico($tipoDocumento = 'FE') } } - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - // Start Otros element $xmlString .= ''; @@ -1237,14 +1241,8 @@ function genXMLGenerico($tipoDocumento = 'FE') // // - switch ($tipoDocumento) { - case 'FE': - $xmlString .= ''; - break; - case 'TE': - $xmlString .= ''; - break; - } + // Cierre del XML + $xmlString .= $xmlClosingTag; // eliminar espacios en blanco para reducir el tamaño del XML $xmlString = preg_replace('/>\s+<', $xmlString); @@ -1252,6 +1250,8 @@ function genXMLGenerico($tipoDocumento = 'FE') $validacion = ValidadorXML::validateXml($xmlString, $consecutivo); if ($validacion && $validacion->status == "error") { + unset($validacion->status); + $validacion->schema = basename($validacion->schema); if (conf_get('mode', 'core', 'web') == 'cli') { return tools_reply([ "Status" => 400, @@ -1277,3443 +1277,6 @@ function genXMLGenerico($tipoDocumento = 'FE') ); } -function genXMLNC() -{ - - // Datos contribuyente - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $codigoActividadReceptor = params_get("codigo_actividad_receptor"); - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - // Datos emisor - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - // Datos receptor - $omitir_receptor = params_get("omitir_receptor"); // Deprecated - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorProvincia = params_get("receptor_provincia"); - $receptorCanton = params_get("receptor_canton"); - $receptorDistrito = params_get("receptor_distrito"); - $receptorBarrio = params_get("receptor_barrio"); - $receptorOtrasSenas = params_get("receptor_otras_senas"); - $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - // Detalles de tiquete / Factura - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerado = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravado = params_get("total_gravado"); - $totalExento = params_get("total_exento"); - $totalExonerado = params_get("total_exonerado"); - $totalNoSujeto = params_get("total_no_sujeto"); - $totalVenta = params_get("total_venta"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - $totalIVADevuelto = params_get("totalIVADevuelto"); - $totalOtrosCargos = params_get("totalOtrosCargos"); - // $totalComprobante = params_get("total_comprobante"); - - $otros = json_decode(params_get('otros')); - - // Detalles de la compra - $detalles = json_decode(params_get("detalles")); - $informacionReferencia = json_decode(params_get("informacion_referencia")); - $otrosCargos = json_decode(params_get("otrosCargos")); - $mediosPago = json_decode(params_get("medios_pago")); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($mediosPago) && $mediosPago != "") { - grace_debug(params_get("medios_pago")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); - } - - if (isset($otrosCargos) && $otrosCargos != "") { - if (count($otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); - //Delimita el array a solo 15 elementos - $otrosCargos = array_slice($otrosCargos, 0, 15); - } - } - - if (isset($mediosPago) && $mediosPago != "") { - if (count($mediosPago) > 4) { - error_log("otrosCargos: " . count($mediosPago) . " is greater than 4"); - //Delimita el array a solo 4 elementos - $mediosPago = array_slice($mediosPago, 0, 4); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ''; - - if (isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { - $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); - } - - $xmlString .= ' - ' . $codigoActividadReceptor . ''; - } - - $xmlString .= ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - if ($emisorOtrasSenas != '') { - $xmlString .= '' . $emisorOtrasSenas . ''; - } - $xmlString .= ''; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - return tools_reply([ - "Status" => 400, - "text" => "El correo del emisor no cumple con el formato establecido." - ], true); - } - - if ($omitir_receptor != 'true') { - $xmlString .= ' - ' . $receptorNombre . ''; - - if ($receptorTipoIdentif != '' && $receptorNumIdentif != '') { - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - } - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= ' - ' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' - . $receptorOtrasSenasExtranjero . - ''; - } - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - } - - $xmlString .= ''; - } - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ' - '; - - /* EJEMPLO DE DETALLES - { - "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], - "2":["1","Sp","Honorarios","100000","100000","100000","100000"] - } - */ - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - ' . $l . ''; - - if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { - $xmlString .= '' . $d->partidaArancelaria . ''; - } - - if (isset($d->codigoCABYS) && $d->codigoCABYS != "") { - $xmlString .= ' - ' . $d->codigoCABYS . ''; - } - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { - $xmlString .= ''; - $lineas = array_slice($d->detalleSurtido, 0, 20); - foreach ($lineas as $linea) { - $xmlString .= ''; - $xmlString .= '' . $linea->codigoCABYSSurtido . ''; - if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { - $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); - foreach ($codigos as $codigo) { - $xmlString .= ''; - $xmlString .= '' . $codigo->tipoSurtido . ''; - $xmlString .= '' . $codigo->codigoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->cantidadSurtido . ''; - $xmlString .= '' . $linea->unidadMedidaSurtido . ''; - if (isset($linea->unidadMedidaComercialSurtido)) { - $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; - } - $xmlString .= '' . $linea->detalleSurtido . ''; - $xmlString .= '' . $linea->precioUnitarioSurtido . ''; - $xmlString .= '' . $linea->montoTotalSurtido . ''; - if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { - $descuentos = array_slice($linea->descuentoSurtido, 0, 5); - foreach ($descuentos as $desc) { - $xmlString .= ''; - $xmlString .= '' . $desc->montoDescuentoSurtido . ''; - $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; - if (isset($desc->descuentoSurtidoOtros)) { - $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; - } - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->subTotalSurtido . ''; - if (isset($linea->ivaCobradoFabricaSurtido)) { - $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; - } - $xmlString .= '' . $linea->baseImponibleSurtido . ''; - if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { - $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); - foreach ($impuestos as $imp) { - $xmlString .= ''; - $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; - if (isset($imp->codigoImpuestoOTROSurtido)) { - $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; - } - if (isset($imp->codigoTarifaIVASurtido)) { - $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; - } - if (isset($imp->tarifaSurtido)) { - $xmlString .= '' . $imp->tarifaSurtido . ''; - } - if (isset($imp->datosImpuestoEspecificoSurtido)) { - $e = $imp->datosImpuestoEspecificoSurtido; - $xmlString .= ''; - if (isset($e->cantidadUnidadMedidaSurtido)) { - $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; - } - if (isset($e->porcentajeSurtido)) { - $xmlString .= '' . $e->porcentajeSurtido . ''; - } - if (isset($e->proporcionSurtido)) { - $xmlString .= '' . $e->proporcionSurtido . ''; - } - if (isset($e->volumenUnidadConsumoSurtido)) { - $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; - } - if (isset($e->impuestoUnidadSurtido)) { - $xmlString .= '' . $e->impuestoUnidadSurtido . ''; - } - $xmlString .= ''; - } - $xmlString .= '' . $imp->montoImpuestoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= ''; - } - $xmlString .= ''; - } - - $xmlString .= ' - ' . round(floatval($d->precioUnitario), DECIMALES) . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . round(floatval($c['montoDescuento']), DECIMALES) . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { - $xmlString .= '' . $d->IVACobradoFabrica . ''; - } - - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - ' . $i->codigo . ''; - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - if ( - isset($i->codigo) && - in_array($i->codigo, ["03", "04", "05", "06"]) && - isset($i->datosImpuestoEspecifico) && - is_object($i->datosImpuestoEspecifico) - ) { - $datosImpuestoEsp = $i->datosImpuestoEspecifico; - $xmlString .= ''; - if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { - $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; - } - if (isset($datosImpuestoEsp->porcentaje)) { - $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; - } - if (isset($datosImpuestoEsp->proporcion)) { - $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; - } - if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { - $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; - } - if (isset($datosImpuestoEsp->impuestoUnidad)) { - $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; - } - $xmlString .= ''; - } - - $xmlString .= '' . $i->monto . ''; - - if (isset($i->montoExportacion) && $i->montoExportacion != "") { - $xmlString .= '' . $i->montoExportacion . ''; - } - - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' - - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; - } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; - } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; - } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; - } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' - '; - } - - $xmlString .= ''; - } - } - if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { - $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; - } - $xmlString .= '' . $d->impuestoNeto . ''; - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerado != '') { - $xmlString .= ' - ' . $totalServExonerado . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if ($totalIVADevuelto != '') { - $xmlString .= ' - ' . $totalIVADevuelto . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} - -function genXMLND() -{ - - // Datos contribuyente - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $codigoActividadReceptor = params_get("codigo_actividad_receptor"); - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - // Datos emisor - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - // Datos receptor - $omitir_receptor = params_get("omitir_receptor"); // Deprecated - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorProvincia = params_get("receptor_provincia"); - $receptorCanton = params_get("receptor_canton"); - $receptorDistrito = params_get("receptor_distrito"); - $receptorBarrio = params_get("receptor_barrio"); - $receptorOtrasSenas = params_get("receptor_otras_senas"); - $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - // Detalles de tiquete / Factura - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerado = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravado = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalExonerado = params_get("total_exonerado"); - $totalNoSujeto = params_get("total_no_sujeto"); - $totalVenta = params_get("total_ventas"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - $totalIVADevuelto = params_get("totalIVADevuelto"); - $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); - $otros = json_decode(params_get('otros')); - - // Detalles de la compra - $detalles = json_decode(params_get("detalles")); - $informacionReferencia = json_decode(params_get("informacion_referencia")); - $otrosCargos = json_decode(params_get("otrosCargos")); - $mediosPago = json_decode(params_get("medios_pago")); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($mediosPago) && $mediosPago != "") { - grace_debug(params_get("medios_pago")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); - } - - if (isset($otrosCargos) && $otrosCargos != "") { - if (count($otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); - //Delimita el array a solo 15 elementos - $otrosCargos = array_slice($otrosCargos, 0, 15); - } - } - - if (isset($mediosPago) && $mediosPago != "") { - if (count($mediosPago) > 4) { - error_log("medios_pago: " . count($mediosPago) . " is greater than 4"); - //Delimita el array a solo 4 elementos - $mediosPago = array_slice($mediosPago, 0, 4); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ''; - - if (isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { - $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); - } - - $xmlString .= ' - ' . $codigoActividadReceptor . ''; - } - - $xmlString .= ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); - } - - if ($omitir_receptor != 'true') { - $xmlString .= ' - ' . $receptorNombre . ''; - - if ($receptorTipoIdentif != '' && $receptorNumIdentif != '') { - $xmlString .= ' - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - } - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= '' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' - . $receptorOtrasSenasExtranjero . - ''; - } - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - } - - $xmlString .= ''; - } - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ' - '; - - /* EJEMPLO DE DETALLES - { - "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], - "2":["1","Sp","Honorarios","100000","100000","100000","100000"] - } - */ - - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - - ' . $l . ''; - if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { - $xmlString .= '' . $d->partidaArancelaria . ''; - } - - if (isset($d->codigoCABYS) && $d->codigoCABYS != "") { - $xmlString .= ' - ' . $d->codigoCABYS . ''; - } - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { - $xmlString .= ''; - $lineas = array_slice($d->detalleSurtido, 0, 20); - foreach ($lineas as $linea) { - $xmlString .= ''; - $xmlString .= '' . $linea->codigoCABYSSurtido . ''; - if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { - $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); - foreach ($codigos as $codigo) { - $xmlString .= ''; - $xmlString .= '' . $codigo->tipoSurtido . ''; - $xmlString .= '' . $codigo->codigoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->cantidadSurtido . ''; - $xmlString .= '' . $linea->unidadMedidaSurtido . ''; - if (isset($linea->unidadMedidaComercialSurtido)) { - $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; - } - $xmlString .= '' . $linea->detalleSurtido . ''; - $xmlString .= '' . $linea->precioUnitarioSurtido . ''; - $xmlString .= '' . $linea->montoTotalSurtido . ''; - if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { - $descuentos = array_slice($linea->descuentoSurtido, 0, 5); - foreach ($descuentos as $desc) { - $xmlString .= ''; - $xmlString .= '' . $desc->montoDescuentoSurtido . ''; - $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; - if (isset($desc->descuentoSurtidoOtros)) { - $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; - } - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->subTotalSurtido . ''; - if (isset($linea->ivaCobradoFabricaSurtido)) { - $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; - } - $xmlString .= '' . $linea->baseImponibleSurtido . ''; - if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { - $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); - foreach ($impuestos as $imp) { - $xmlString .= ''; - $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; - if (isset($imp->codigoImpuestoOTROSurtido)) { - $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; - } - if (isset($imp->codigoTarifaIVASurtido)) { - $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; - } - if (isset($imp->tarifaSurtido)) { - $xmlString .= '' . $imp->tarifaSurtido . ''; - } - if (isset($imp->datosImpuestoEspecificoSurtido)) { - $e = $imp->datosImpuestoEspecificoSurtido; - $xmlString .= ''; - if (isset($e->cantidadUnidadMedidaSurtido)) { - $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; - } - if (isset($e->porcentajeSurtido)) { - $xmlString .= '' . $e->porcentajeSurtido . ''; - } - if (isset($e->proporcionSurtido)) { - $xmlString .= '' . $e->proporcionSurtido . ''; - } - if (isset($e->volumenUnidadConsumoSurtido)) { - $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; - } - if (isset($e->impuestoUnidadSurtido)) { - $xmlString .= '' . $e->impuestoUnidadSurtido . ''; - } - $xmlString .= ''; - } - $xmlString .= '' . $imp->montoImpuestoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= ''; - } - $xmlString .= ''; - } - - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . $c['montoDescuento'] . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { - $xmlString .= '' . $d->IVACobradoFabrica . ''; - } - - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - ' . $i->codigo . ''; - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - if ( - isset($i->codigo) && - in_array($i->codigo, ["03", "04", "05", "06"]) && - isset($i->datosImpuestoEspecifico) && - is_object($i->datosImpuestoEspecifico) - ) { - $datosImpuestoEsp = $i->datosImpuestoEspecifico; - $xmlString .= ''; - if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { - $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; - } - if (isset($datosImpuestoEsp->porcentaje)) { - $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; - } - if (isset($datosImpuestoEsp->proporcion)) { - $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; - } - if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { - $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; - } - if (isset($datosImpuestoEsp->impuestoUnidad)) { - $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; - } - $xmlString .= ''; - } - - $xmlString .= '' . $i->monto . ''; - - if (isset($i->montoExportacion) && $i->montoExportacion != "") { - $xmlString .= '' . $i->montoExportacion . ''; - } - - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' - - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; - } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; - } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; - } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; - } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' - '; - } - - $xmlString .= ''; - } - } - if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { - $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; - } - $xmlString .= '' . $d->impuestoNeto . ''; - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerado != '') { - $xmlString .= ' - ' . $totalServExonerado . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if ($totalIVADevuelto != '') { - $xmlString .= ' - ' . $totalIVADevuelto . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} - -function genXMLMr() -{ - - $clave = params_get("clave"); // d{50,50} - // Datos vendedor = emisor - $numeroCedulaEmisor = params_get("numero_cedula_emisor"); // d{12,12} cedula fisica,juridica,NITE,DIMEX - $numeroCedulaEmisor = str_pad($numeroCedulaEmisor, 12, "0", STR_PAD_LEFT); - - // Datos mensaje receptor - $fechaEmisionDoc = params_get("fecha_emision_doc"); // fecha de emision de la confirmacion - $mensaje = params_get("mensaje"); // 1 - Aceptado, 2 - Aceptado Parcialmente, 3 - Rechazado - $detalleMensaje = params_get("detalle_mensaje"); - $montoTotalImpuesto = params_get("monto_total_impuesto"); // d18,5 opcional /obligatorio si comprobante tenga impuesto - $codigoActividad = params_get("codigo_actividad"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $totalFactura = params_get("total_factura"); // d18,5 - $numeroConsecutivoReceptor = params_get("numero_consecutivo_receptor"); // d{20,20} numeracion consecutiva de los mensajes de confirmacion - - // Datos comprador = receptor - $numeroCedulaReceptor = params_get("numero_cedula_receptor"); // d{12,12}cedula fisica, juridica, NITE, DIMEX del comprador - $numeroCedulaReceptor = str_pad($numeroCedulaReceptor, 12, "0", STR_PAD_LEFT); - - // Validate string sizes - $codigoActividad = str_pad($codigoActividad, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividad) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize: " . CODIGOACTIVIDADSIZE . " is not codigoActividad: " . $codigoActividad); - } - - $xmlString = ' - - ' . $clave . ' - ' . $numeroCedulaEmisor . ' - ' . $fechaEmisionDoc . ' - ' . $mensaje . ''; - if (!empty($detalleMensaje)) { - $xmlString .= '' . $detalleMensaje . ''; - } - - if (!empty($montoTotalImpuesto)) { - $xmlString .= '' . $montoTotalImpuesto . ''; - } - $xmlString .= '' . $codigoActividad . ' - ' . $totalFactura . ' - ' . $numeroCedulaReceptor . ' - ' . $numeroConsecutivoReceptor . ''; - - $xmlString .= ''; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); -} - -function genXMLFec() -{ - // Datos contribuyente - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $codigoActividadReceptor = params_get("codigo_actividad_receptor"); - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - // Datos emisor - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorOtrasSenasExtranjero = params_get("emisor_otras_senas_extranjero"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - // Datos receptor - // Deprecated - $omitir_receptor = params_get("omitir_receptor"); - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorProvincia = params_get("receptor_provincia"); - $receptorCanton = params_get("receptor_canton"); - $receptorDistrito = params_get("receptor_distrito"); - $receptorBarrio = params_get("receptor_barrio"); - $receptorOtrasSenas = params_get("receptor_otras_senas"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - // Detalles de tiquete / Factura - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerado = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravado = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalExonerado = params_get("total_exonerado"); - $totalNoSujeto = params_get("total_no_sujeto"); - $totalVenta = params_get("total_ventas"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - - $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); - $otros = json_decode(params_get('otros')); - - // Detalles de la compra - $detalles = json_decode(params_get("detalles")); - $informacionReferencia = json_decode(params_get("informacion_referencia")); - - $otrosCargos = json_decode(params_get("otrosCargos")); - $mediosPago = json_decode(params_get("medios_pago")); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - grace_debug(params_get("detalles")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($mediosPago) && $mediosPago != "") { - grace_debug(params_get("medios_pago")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); - } - - if (isset($otrosCargos) && $otrosCargos != "") { - if (count($otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); - //Delimita el array a solo 15 elementos - $otrosCargos = array_slice($otrosCargos, 0, 15); - } - } - - if (isset($mediosPago) && $mediosPago != "") { - if (count($mediosPago) > 4) { - error_log("mediosPago: " . count($mediosPago) . " is greater than 4"); - //Delimita el array a solo 4 elementos - $mediosPago = array_slice($mediosPago, 0, 4); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ' - ' . $codigoActividadReceptor . ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; - } - - if ($emisorOtrasSenasExtranjero != '' && strlen($emisorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' . $emisorOtrasSenasExtranjero . ''; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if ($emisorEmail != '' && preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - tools_reply([ - "Status" => 400, - "text" => "El email del emisor ($emisorEmail) no tiene un formato válido.", - ], true); - } - - - $xmlString .= ' - ' . $receptorNombre . ''; - - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= '' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - } - - $xmlString .= ''; - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ' - '; - - // cant - unidad medida - detalle - precio unitario - monto total - subtotal - monto total linea - Monto desc -Naturaleza Desc - Impuesto : Codigo / Tarifa / Monto - /* EJEMPLO DE DETALLES - { - "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], - "2":["1","Sp","Honorarios","100000","100000","100000","100000"] - } - */ - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - - ' . $l . ''; - - $xmlString .= ' - ' . $d->codigoCABYS . ''; - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . $c['montoDescuento'] . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } - - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - - ' . $i->codigo . ''; - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - $xmlString .= '' . $i->monto . ''; - - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' - - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; - } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; - } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; - } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; - } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' - '; - } - - $xmlString .= ''; - } - } - - $xmlString .= '' . $d->impuestoNeto . ''; - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerado != '') { - $xmlString .= ' - ' . $totalServExonerado . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} - -function genXMLFee() -{ - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $detalles = json_decode(params_get("detalles")); - $otrosCargos = json_decode(params_get("otrosCargos")); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalGravado = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalVenta = params_get("total_ventas"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); - - $informacionReferencia = json_decode(params_get("informacion_referencia")); - $otros = json_decode(params_get('otros')); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - grace_debug(params_get("detalles")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenasExtranjero) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenasExtranjero); - } - - if (isset($otrosCargos) && !empty($otrosCargos)) { - if (count($otrosCargos->otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos->otrosCargos) . " is greater than 15"); - //Delimita el array a solo 4 elementos - $otrosCargos->otrosCargos = array_slice($otrosCargos->otrosCargos, 0, 15); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); - } - - if (isset($receptorNombre) && $receptorNombre != "") { - $xmlString .= ' - ' . $receptorNombre . ''; - } - - if (isset($receptorTipoIdentif) && $receptorTipoIdentif != "" && isset($receptorNumIdentif) && $receptorNumIdentif != "") { - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - } - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if (isset($receptorProvincia) && $receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= '' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' - . $receptorOtrasSenasExtranjero . - ''; - } - - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - $xmlString .= ''; - } - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ' - '; - - - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - - ' . $l . ''; - - if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { - $xmlString .= ' - ' . $d->partidaArancelaria . ''; - } - - $xmlString .= ' - ' . $d->codigoCABYS . ''; - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { - $xmlString .= ''; - $lineas = array_slice($d->detalleSurtido, 0, 20); - foreach ($lineas as $linea) { - $xmlString .= ''; - $xmlString .= '' . $linea->codigoCABYSSurtido . ''; - if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { - $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); - foreach ($codigos as $codigo) { - $xmlString .= ''; - $xmlString .= '' . $codigo->tipoSurtido . ''; - $xmlString .= '' . $codigo->codigoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->cantidadSurtido . ''; - $xmlString .= '' . $linea->unidadMedidaSurtido . ''; - if (isset($linea->unidadMedidaComercialSurtido)) { - $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; - } - $xmlString .= '' . $linea->detalleSurtido . ''; - $xmlString .= '' . $linea->precioUnitarioSurtido . ''; - $xmlString .= '' . $linea->montoTotalSurtido . ''; - if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { - $descuentos = array_slice($linea->descuentoSurtido, 0, 5); - foreach ($descuentos as $desc) { - $xmlString .= ''; - $xmlString .= '' . $desc->montoDescuentoSurtido . ''; - $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; - if (isset($desc->descuentoSurtidoOtros)) { - $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; - } - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->subTotalSurtido . ''; - if (isset($linea->ivaCobradoFabricaSurtido)) { - $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; - } - $xmlString .= '' . $linea->baseImponibleSurtido . ''; - if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { - $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); - foreach ($impuestos as $imp) { - $xmlString .= ''; - $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; - if (isset($imp->codigoImpuestoOTROSurtido)) { - $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; - } - if (isset($imp->codigoTarifaIVASurtido)) { - $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; - } - if (isset($imp->tarifaSurtido)) { - $xmlString .= '' . $imp->tarifaSurtido . ''; - } - if (isset($imp->datosImpuestoEspecificoSurtido)) { - $e = $imp->datosImpuestoEspecificoSurtido; - $xmlString .= ''; - if (isset($e->cantidadUnidadMedidaSurtido)) { - $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; - } - if (isset($e->porcentajeSurtido)) { - $xmlString .= '' . $e->porcentajeSurtido . ''; - } - if (isset($e->proporcionSurtido)) { - $xmlString .= '' . $e->proporcionSurtido . ''; - } - if (isset($e->volumenUnidadConsumoSurtido)) { - $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; - } - if (isset($e->impuestoUnidadSurtido)) { - $xmlString .= '' . $e->impuestoUnidadSurtido . ''; - } - $xmlString .= ''; - } - $xmlString .= '' . $imp->montoImpuestoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= ''; - } - $xmlString .= ''; - } - - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . $c['montoDescuento'] . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - '; - if (isset($i->codigo) && $i->codigo != "") { - $xmlString .= '' . $i->codigo . ''; - } - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - if (isset($i->monto) && $i->monto != "") { - $xmlString .= '' . $i->monto . ''; - } - - if (isset($i->montoExportacion) && $i->montoExportacion != "") { - $xmlString .= '' . $i->montoExportacion . ''; - } - - $xmlString .= ''; - } - } - - if (isset($d->impuestoNeto) && $d->impuestoNeto != "") { - $xmlString .= '' . $d->impuestoNeto . ''; - } - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - - // JSON DE EJEMPLO - // [ - // { - // "tipoDocumentoOC": "10", - // "tipoDocumentoOTROS": "string", - // "tipoIdentidadTercero": "01", - // "numeroIdentidadTercero": "160029688", - // "nombreTercero": "John Doe", - // "detalle": "Additional charge for service", - // "porcentajeOC": "1452590.23", - // "montoCargo": "1258720.23491" - // }, - // { - // "tipoDocumentoOC": "20", - // "tipoDocumentoOTROS": "other", - // "tipoIdentidadTercero": "02", - // "numeroIdentidadTercero": "123456789", - // "nombreTercero": "Jane Smith", - // "detalle": "Extra fee for expedited processing", - // "porcentajeOC": "10.50", - // "montoCargo": "500.00" - // } - // ] - - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - // XML Resultante - // - // 10 - // string - // - // 01 - // 160029688 - // - // John Doe - // Additional charge for service - // 1452590.23 - // 1258720.23491 - // - // - // 20 - // other - // - // 02 - // 123456789 - // - // Jane Smith - // Extra fee for expedited processing - // 10.50 - // 500.00 - // - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - // JSON de ejemplo - // { - // "informacionReferencia": [ - // { - // "tipoDoc": "01", - // "tipoDocOtro": "Factura", - // "numero": "50620032400020536006000100001010000000017100000017", - // "fechaEmision": "2023-10-01T12:00:00", - // "codigo": "99", - // "codigoOtro": "OTRO1", - // "razon": "Corrección de datos" - // }, - // { - // "tipoDoc": "02", - // "numero": "50620032400020536006000100001010000000017200000018", - // "fechaEmision": "2023-10-02T15:30:00", - // "codigo": "01", - // "razon": "Devolución de producto" - // } - // ] - // } - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // XML Resultante - // - // 01 - // Factura - // 50620032400020536006000100001010000000017100000017 - // 2023-10-01T12:00:00 - // 99 - // OTRO1 - // Corrección de datos - // - // - // 02 - // 50620032400020536006000100001010000000017200000018 - // 2023-10-02T15:30:00 - // 01 - // Devolución de producto - // - - // ----------------------------------------------------------------------------------------------------- - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} - /* * ************************************************** */ /* Funcion de prueba */ /* * ************************************************** */ diff --git a/api/contrib/genXML/genXML.txt b/api/contrib/genXML/genXML.txt new file mode 100644 index 00000000..9bb1b03c --- /dev/null +++ b/api/contrib/genXML/genXML.txt @@ -0,0 +1,4755 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* * ************************************************** */ +/* Constantes de validacion */ +/* * ************************************************** */ +define("DECIMALES", 5); +define("TIPODOCREFVALUES", array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '99')); +define('CODIDOREFVALUES', array('01', '02', '04', '05', '06', '07', '08', '09', '10', '11', '12', '99')); + +const CODIGOACTIVIDADSIZE = 6; +const EMISORNOMBREMAXSIZE = 100; +const EMISORNUMEROTELMIN = 8; +const EMISORNUMEROTELMAX = 20; +const RECEPTORNOMBREMAXSIZE = 100; +const RECEPTOROTRASSENASMAXSIZE = 250; +const RECEPTOROTRASSENASEXTRANJEROMAXSIZE = 300; +const EMAIL_REGEX = "/^\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*$/"; + +const AUTO_CALCULAR = ["totalServGravados", "totalServExentos", "totalServExonerado", "totalServNoSujeto", "totalMercanciasGravadas", "totalMercanciasExentas", "totalMercExonerada", "totalMercNoSujeta", "totalGravado", "totalExento", "totalExonerado", "totalNoSujeto", "totalVenta", "totalDescuentos", "totalVentaNeta", "totalDesgloseImpuesto", "totalImpuesto"]; + +tools_useTool('ValidadorXML.php'); + +/** + * Devuelve true si todas las claves existen en el array + * + function array_all(array $keys, array $array) { + foreach ($keys as $key) { + if (!array_key_exists($key, $array)) { + return false; + } + } + return true; + } + + * Devuelve true si al menos una de las claves existe en el array + * +function array_any(array $keys, array $array) { + foreach ($keys as $key) { + if (array_key_exists($key, $array)) { + return true; + } + } + return false; +} */ + +/** + * Valida las reglas conocidas de una linea del detalle para evitar rechazos + * + * @param int $numLinea + * @param object $detallesLinea + * @return void + */ +function validarLinea($numLinea, $d) +{ + $d = (array) $d; + + if (isset($d['baseImponible']) && !isset($d['impuesto'])) { + tools_reply([ + "Status" => 400, + "text" => "Detalle de líneas $numLinea: cuando se envía el campo 'baseImponible' es obligatorio enviar también el campo 'impuesto'", + ], true); + } +} + +/** + * Actualiza los campos auto calculables usando los detalles de la linea + * + * @param int $numLinea + * @param object $detallesLinea + * @param array &$calculados + * @return void + */ +function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) +{ + + if (!isset($calculados['totalDescuentos'])) { + $calculados['totalDescuentos'] = 0; + } + $calculados['totalDescuentos'] += round($totalDescuentos, DECIMALES); + + if (!isset($calculados[$numLinea])) { + $calculados[$numLinea] = []; + } + + // Se obtiene de la resta del campo monto total menos monto de descuento concedido + $calculados[$numLinea]['montoTotal'] = round($d->precioUnitario * $d->cantidad, DECIMALES); + $calculados[$numLinea]['subTotal'] = round($calculados[$numLinea]['montoTotal'] - $totalDescuentos, DECIMALES); + + /* + @TODO base imponible puede incluir + Este campo será de condición obligatoria, cuando el producto/ servicio este gravado con algún impuesto. + Se obtiene de la suma entre el campo "Subtotal", más + el impuesto selectivo de consumo (02), + el Impuesto específico de Bebidas Alcohólicas (04), + el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05) y + el impuesto al cemento (12), cuando corresponda. + Este campo se podrá editar cuando se seleccione + en el campo "IVA cobrado a nivel de fábrica" el Código 01 + o en el campo de "Código del impuesto" el código 07. + */ + + $calculados[$numLinea]['baseImponible'] = $calculados[$numLinea]['subTotal']; + + foreach (AUTO_CALCULAR as $field) { + if (!isset($calculados[$field])) { + if ($field !== 'totalDesgloseImpuesto') { + $calculados[$field] = 0; + continue; + } + $calculados[$field] = []; + } + } + +} + +/** + * Elimina caracteres no permitidos + * Solo letras, numeros, espacios y signos de puntuacion comunes en detalle + */ +function limpiarTexto($texto, $largoMaximo = FALSE) +{ + $limpio = preg_replace('/[^\\p{L}\\p{N}\\s.,;:\\-\\_\\(\\)\\[\\]\\{\\}\\!\\?\'"\\n\\r]/u', '', $texto); + + if ($largoMaximo === FALSE) { + return $limpio; + } + // Trunca a la longitud máxima permitida + return mb_substr($limpio, 0, $largoMaximo); +} + +/* * ************************************************** */ +/* Funcion para generar XML */ +/* * ************************************************** */ + +function genXMLFe() +{ + return genXMLGenerico('FE'); +} + +function genXMLTe() +{ + return genXMLGenerico('TE'); +} + +function genXMLNc() +{ + return genXMLGenerico('NC'); +} + +function genXMLNd() +{ + return genXMLGenerico('ND'); +} + +function genXMLGenerico($tipoDocumento = 'FE') +{ + // Datos contribuyente + $clave = params_get("clave"); + $proveedorSistemas = params_get("proveedor_sistemas"); + $codigoActividadEmisor = params_get("codigo_actividad_emisor"); + // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json + $codigoActividadReceptor = params_get("codigo_actividad_receptor"); + $consecutivo = params_get("consecutivo"); + $fechaEmision = params_get("fecha_emision"); + + // Datos emisor + $emisorNombre = params_get("emisor_nombre"); + $emisorTipoIdentif = params_get("emisor_tipo_identif"); + $emisorNumIdentif = params_get("emisor_num_identif"); + $emisorNombreComercial = params_get("emisor_nombre_comercial"); + $emisorProv = params_get("emisor_provincia"); + $emisorCanton = params_get("emisor_canton"); + $emisorDistrito = params_get("emisor_distrito"); + $emisorBarrio = params_get("emisor_barrio"); + $emisorOtrasSenas = params_get("emisor_otras_senas"); + $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); + $emisorTel = params_get("emisor_tel"); + // This API only supports one email address for emisor + $emisorEmail = params_get("emisor_email"); + $registroFiscal8707 = params_get("registrofiscal8707"); + + // Datos receptor + $receptorNombre = params_get("receptor_nombre"); + $receptorTipoIdentif = params_get("receptor_tipo_identif"); + $receptorNumIdentif = params_get("receptor_num_identif"); + $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); + $receptorNombreComercial = params_get("receptor_nombre_comercial"); + $receptorProvincia = params_get("receptor_provincia"); + $receptorCanton = params_get("receptor_canton"); + $receptorDistrito = params_get("receptor_distrito"); + $receptorBarrio = params_get("receptor_barrio"); + $receptorOtrasSenas = params_get("receptor_otras_senas"); + $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); + $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); + $receptorTel = params_get("receptor_tel"); + $receptorEmail = params_get("receptor_email"); + + // Detalles de tiquete / Factura + $condVenta = params_get("condicion_venta"); + $condVentaOtros = params_get("condicion_venta_otros"); + $plazoCredito = params_get("plazo_credito"); + $codMoneda = params_get("cod_moneda"); + $tipoCambio = params_get("tipo_cambio"); + + $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); + $totalIVADevuelto = params_get("totalIVADevuelto"); + $totalOtrosCargos = params_get("totalOtrosCargos"); + + $otros = json_decode(params_get('otros')); + + // Detalles de la compra + $detalles = json_decode(params_get("detalles")); + $informacionReferencia = json_decode(params_get("informacion_referencia")); + $otrosCargos = json_decode(params_get("otrosCargos")); + $mediosPago = json_decode(params_get("medios_pago")); + + // Resumen + $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); + + grace_debug(params_get("detalles")); + + if (isset($otrosCargos) && $otrosCargos != "") { + grace_debug(params_get("otrosCargos")); + } + + if (isset($mediosPago) && $mediosPago != "") { + grace_debug(params_get("medios_pago")); + } + + if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { + grace_debug(params_get("totalDesgloseImpuesto")); + } + + // Validate string sizes + $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); + } + + if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { + error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); + } + + if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { + error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); + } + + if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { + error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); + } + + if (isset($otrosCargos) && $otrosCargos != "") { + if (count($otrosCargos) > 15) { + error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); + //Delimita el array a solo 15 elementos + $otrosCargos = array_slice($otrosCargos, 0, 15); + } + } + + if (isset($mediosPago) && $mediosPago != "") { + if (count($mediosPago) > 4) { + error_log("mediosPago: " . count($mediosPago) . " is greater than 4"); + //Delimita el array a solo 4 elementos + $mediosPago = array_slice($mediosPago, 0, 4); + } + } else { + if (!in_array($condVenta, ['02', '08', '10'], true)) { + tools_reply([ + "Status" => 400, + "text" => [ + "medios_pago" => $mediosPago, + "mensaje" => "El campo 'medios_pago' es obligatorio cuando 'condicion_venta' no es '02', '08' o '10'" + ] + ], true); + } + } + + $xmlClosingTag = ''; + + switch ($tipoDocumento) { + case 'FE': + $xmlString = ' + '; + $xmlClosingTag = ''; + break; + case 'TE': + $xmlString = ' + '; + $xmlClosingTag = ''; + break; + case 'NC': + $xmlString = ' + '; + $xmlClosingTag = ''; + break; + case 'ND': + $xmlString = ' + '; + $xmlClosingTag = ''; + break; + default: + tools_reply([ + "Status" => 400, + "text" => "El campo 'tipoDocumento' debe ser uno de los siguientes valores: FE, TE, NC, ND" + ], true); + } + + $xmlString .= '' . $clave . ' + ' . $proveedorSistemas . ' + ' . $codigoActividadEmisor . ''; + + if ($tipoDocumento == "FE" && isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { + $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); + } + + $xmlString .= ' + ' . $codigoActividadReceptor . ''; + } + + $xmlString .= ' + ' . $consecutivo . ' + ' . $fechaEmision . ' + + ' . $emisorNombre . ' + + ' . $emisorTipoIdentif . ' + ' . $emisorNumIdentif . ' + '; + + if (isset($registroFiscal8707) && $registroFiscal8707 != "") { + $xmlString .= ' + ' . $registroFiscal8707 . ''; + } + + if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { + $xmlString .= ' + ' . $emisorNombreComercial . ''; + } + + if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito) { + $xmlString .= ' + ' . $emisorProv . ' + ' . $emisorCanton . ' + ' . $emisorDistrito . ''; + if ($emisorBarrio != '') { + $xmlString .= '' . $emisorBarrio . ''; + } + $xmlString .= '123456'; + + $xmlString .= ''; + } + + if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { + $xmlString .= ' + + ' . $emisorCodPaisTel . ' + ' . $emisorTel . ' + '; + } + + if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { + $xmlString .= '' . trim($emisorEmail) . ''; + } else { + error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); + } + + $xmlString .= ' + ' . $receptorNombre . ''; + + if (!empty($receptorTipoIdentif) && !empty($receptorNumIdentif)) { + $xmlString .= ' + + ' . $receptorTipoIdentif . ' + ' . $receptorNumIdentif . ' + '; + } + + if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { + $xmlString .= ' + ' + . $receptorIdentifExtranjero . + ''; + } + + if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { + $xmlString .= ' + ' . $receptorNombreComercial . ''; + } + + if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { + $xmlString .= ' + + ' . $receptorProvincia . ' + ' . $receptorCanton . ' + ' . $receptorDistrito . ''; + if ($receptorBarrio != '') { + $xmlString .= '' . $receptorBarrio . ''; + } + $xmlString .= ' + ' . $receptorOtrasSenas . ' + '; + } + + if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { + $xmlString .= ' + ' + . $receptorOtrasSenasExtranjero . + ''; + } + + if ($receptorCodPaisTel != '' && $receptorTel != '') { + $xmlString .= ' + + ' . $receptorCodPaisTel . ' + ' . $receptorTel . ' + '; + } + + if ($receptorEmail != '') { + $xmlString .= '' . $receptorEmail . ''; + } + + $xmlString .= ''; + + $xmlString .= ' + ' . $condVenta . ''; + + if (isset($condVentaOtros) && $condVentaOtros != "") { + $xmlString .= ' + ' . $condVentaOtros . ''; + } + + if (isset($plazoCredito) && $plazoCredito != "") { + $xmlString .= ' + ' . $plazoCredito . ''; + } else { + // es credito segun la condicion de venta + if ($condVenta == "02") { + tools_reply([ + "Status" => 400, + "text" => "El campo 'plazoCredito' es obligatorio cuando 'condicionVenta' es '02'" + ], true); + } + } + + $xmlString .= ' + '; + + /* EJEMPLO DE DETALLES + [ + { + "codigoCABYS": "101010101", + "codigoComercial": [ + { "tipo": "01", "codigo": "A123" }, + { "tipo": "02", "codigo": "B456" } + ], + "cantidad": 2, + "unidadMedida": "Unid", + "tipoTransaccion": "Venta", + "unidadMedidaComercial": "Caja", + "detalle": "Medicamento genérico", + "numeroVINoSerie": "VIN123456789", + "registroMedicamento": "REG-CR-2024-0001", + "formaFarmaceutica": "TAB", + "detalleSurtido": [ + { + "codigoCABYSSurtido": "202020202", + "codigoComercialSurtido": [ + { "tipoSurtido": "01", "codigoSurtido": "S123" } + ], + "cantidadSurtido": 1, + "unidadMedidaSurtido": "Unid", + "unidadMedidaComercialSurtido": "Blister", + "detalleSurtido": "Surtido de medicamento", + "precioUnitarioSurtido": 120.00, + "montoTotalSurtido": 120.00, + "descuentoSurtido": [ + { + "montoDescuentoSurtido": 10.00, + "codigoDescuentoSurtido": "01", + "descuentoSurtidoOtros": "Descuento especial" + } + ], + "subTotalSurtido": 110.00, + "ivaCobradoFabricaSurtido": 5.00, + "baseImponibleSurtido": 105.00, + "impuestoSurtido": [ + { + "codigoImpuestoSurtido": "01", + "codigoTarifaIVASurtido": "08", + "tarifaSurtido": 13.00, + "montoImpuestoSurtido": 13.65, + "datosImpuestoEspecificoSurtido": { + "cantidadUnidadMedidaSurtido": 1, + "porcentajeSurtido": 5.0, + "proporcionSurtido": 0.5, + "volumenUnidadConsumoSurtido": 0.1, + "impuestoUnidadSurtido": 2.00 + } + } + ] + } + ], + "precioUnitario": 150.00, + "montoTotal": 300.00, + "descuento": [ + { + "montoDescuento": 20.00, + "codigoDescuento": "99", + "codigoDescuentoOTRO": "DESC-OTRO-001", + "naturalezaDescuento": "Descuento por promoción" + } + ], + "subTotal": 280.00, + "IVACobradoFabrica": 10.00, + "baseImponible": 270.00, + "impuesto": [ + { + "codigo": "01", + "codigoTarifa": "08", + "tarifa": 13.00, + "factorIVA": 1.0, + "monto": 35.10, + "exoneracion": { + "tipoDocumento": "01", + "tipoDocumentoOtro": "OTRODOC", + "numeroDocumento": "EXON-2024-001", + "numeroArticulo": "ART-01", + "numeroInciso": "INC-01", + "nombreInstitucion": "Ministerio de Salud", + "nombreInstitucionOtros": "Otra Institución", + "fechaEmision": "2024-06-01", + "tarifaExoneracion": 50.0, + "montoExoneracion": 17.55 + } + }, + { + "codigo": "03", + "codigoTarifa": "01", + "tarifa": 2.00, + "factorIVA": 0.5, + "monto": 5.00, + "datosImpuestoEspecifico": { + "cantidadUnidadMedida": 2, + "porcentaje": 10.0, + "proporcion": 0.2, + "volumenUnidadConsumo": 0.5, + "impuestoUnidad": 1.00 + } + } + ], + "impuestoAsumidoEmisorFabrica": 2.00, + "impuestoNeto": 22.55, + "montoTotalLinea": 302.55 + }, + { + "codigoCABYS": "303030303", + "cantidad": 1, + "unidadMedida": "Kg", + "detalle": "Producto sin surtido ni descuentos", + "precioUnitario": 50.00, + "montoTotal": 50.00, + "subTotal": 50.00, + "baseImponible": 50.00, + "impuesto": [ + { + "codigo": "01", + "codigoTarifa": "08", + "tarifa": 13.00, + "monto": 6.50 + } + ], + "montoTotalLinea": 56.50 + } + ] + */ + + $l = 1; + $calculados = []; + + foreach ($detalles as $d) { + + foreach (["codigoCABYS"] as $requiredField) { + if (!isset($d->{$requiredField}) || $d->{$requiredField} === '') { + tools_reply("Se requiere el campo $requiredField en el detalle #$l", true); + } + } + + $xmlString .= ' + + ' . $l . ''; + + $xmlString .= ' + ' . $d->codigoCABYS . ''; + + if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { + // Convertir el objeto $d->codigoComercial en un array + $codigoComercialArray = (array) $d->codigoComercial; + + // Delimitar el array a solo 5 elementos + if (count($codigoComercialArray) > 5) { + error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); + } + $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); + + // Iterar sobre los elementos del array + foreach ($codigoComercialArray as $codigos) { + $c = (array) $codigos; + // Verificar si el elemento es un array asociativo + if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { + $xmlString .= ' + + ' . $c['tipo'] . ' + ' . $c['codigo'] . ' + '; + } + } + } + + $xmlString .= ' + ' . $d->cantidad . ' + ' . $d->unidadMedida . ''; + if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { + $xmlString .= ' + ' . $d->tipoTransaccion . ''; + } + if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { + $xmlString .= ' + ' . $d->unidadMedidaComercial . ''; + } + + $d->detalle = limpiarTexto($d->detalle, 150); + + $xmlString .= '' . $d->detalle . ''; + if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { + $xmlString .= '' . $d->numeroVINoSerie . ''; + } + + if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { + $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; + } + if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { + $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; + } + + if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { + $xmlString .= ''; + $lineas = array_slice($d->detalleSurtido, 0, 20); + foreach ($lineas as $linea) { + $xmlString .= ''; + $xmlString .= '' . $linea->codigoCABYSSurtido . ''; + if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { + $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); + foreach ($codigos as $codigo) { + $xmlString .= ''; + $xmlString .= '' . $codigo->tipoSurtido . ''; + $xmlString .= '' . $codigo->codigoSurtido . ''; + $xmlString .= ''; + } + } + $xmlString .= '' . $linea->cantidadSurtido . ''; + $xmlString .= '' . $linea->unidadMedidaSurtido . ''; + if (isset($linea->unidadMedidaComercialSurtido)) { + $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; + } + $xmlString .= '' . $linea->detalleSurtido . ''; + $xmlString .= '' . $linea->precioUnitarioSurtido . ''; + $xmlString .= '' . $linea->montoTotalSurtido . ''; + if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { + $descuentos = array_slice($linea->descuentoSurtido, 0, 5); + foreach ($descuentos as $desc) { + $xmlString .= ''; + $xmlString .= '' . $desc->montoDescuentoSurtido . ''; + $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; + if (isset($desc->descuentoSurtidoOtros)) { + $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; + } + $xmlString .= ''; + } + } + $xmlString .= '' . $linea->subTotalSurtido . ''; + if (isset($linea->ivaCobradoFabricaSurtido)) { + $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; + } + $xmlString .= '' . $linea->baseImponibleSurtido . ''; + if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { + $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); + foreach ($impuestos as $imp) { + $xmlString .= ''; + $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; + if (isset($imp->codigoImpuestoOTROSurtido)) { + $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; + } + if (isset($imp->codigoTarifaIVASurtido)) { + $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; + } + if (isset($imp->tarifaSurtido)) { + $xmlString .= '' . $imp->tarifaSurtido . ''; + } + if (isset($imp->datosImpuestoEspecificoSurtido)) { + $e = $imp->datosImpuestoEspecificoSurtido; + $xmlString .= ''; + if (isset($e->cantidadUnidadMedidaSurtido)) { + $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; + } + if (isset($e->porcentajeSurtido)) { + $xmlString .= '' . $e->porcentajeSurtido . ''; + } + if (isset($e->proporcionSurtido)) { + $xmlString .= '' . $e->proporcionSurtido . ''; + } + if (isset($e->volumenUnidadConsumoSurtido)) { + $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; + } + if (isset($e->impuestoUnidadSurtido)) { + $xmlString .= '' . $e->impuestoUnidadSurtido . ''; + } + $xmlString .= ''; + } + $xmlString .= '' . $imp->montoImpuestoSurtido . ''; + $xmlString .= ''; + } + } + $xmlString .= ''; + } + $xmlString .= ''; + } + + $totalDescuentos = 0; + $xmlStringDescuento = ''; + // Procesar descuentos, si existen + if (isset($d->descuento) && !empty($d->descuento)) { + + $descuentoArray = (array) $d->descuento; + + if (count($descuentoArray) > 5) { + error_log("descuento: " . count($descuentoArray) . " is greater than 5"); + } + $descuentoArray = array_slice($descuentoArray, 0, 5); + + foreach ($descuentoArray as $descuentos) { + $c = (array) $descuentos; + if ( + is_array($c) && + isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && + isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" + ) { + $c['montoDescuento'] = round(floatval($c['montoDescuento']), DECIMALES) * $d->cantidad; + $xmlStringDescuento = ' + ' . $c['montoDescuento'] . ' + ' . $c['codigoDescuento'] . ''; + // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo + if ( + isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && + isset($c['codigoDescuentoOTRO']) && + strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 + ) { + $xmlStringDescuento .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; + } + // NaturalezaDescuento: minOccurs=0, longitud 3-80 + if ( + isset($c['naturalezaDescuento']) && + strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 + ) { + $xmlStringDescuento .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; + } + $xmlStringDescuento .= ''; + $totalDescuentos += $c['montoDescuento']; + } + } + } + + // Calcular los valores que se pueden derivar de los datos ingresados + autoCalcularLinea($l, $d, $totalDescuentos, $calculados); + + $xmlString .= '' . round(floatval($d->precioUnitario), DECIMALES) . ''; + $xmlString .= '' . $calculados[$l]['montoTotal'] . ''; + $xmlString .= $xmlStringDescuento; + $xmlString .= '' . $calculados[$l]['subTotal'] . ''; + + if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { + $xmlString .= '' . $d->IVACobradoFabrica . ''; + } + + $xmlString .= '' . $calculados[$l]['baseImponible'] . ''; + + if (isset($d->impuesto) && $d->impuesto != "") { + foreach ($d->impuesto as $i) { + $xmlString .= ' + + ' . $i->codigo . ''; + + // Add if required + if ( + isset($i->codigo) && $i->codigo == "99" && + isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) + ) { + $xmlString .= '' . $i->codigoImpuestoOtro . ''; + } + + // calcular el monto de impuesto si no se proporciona usando el codigo y la tarifa + if ($i->codigo === "01" && isset($i->codigoTarifa)) { + + $impuestoPorTarifa = [ + "01" => 0, // 0% (Artículo 32, num 1, RLIVA) + "02" => 0.01, // Tarifa reducida 1% + "03" => 0.02, // Tarifa reducida 2% + "04" => 0.04, // Tarifa reducida 4% + "05" => 0, // Transitorio 0% + "06" => 0.04, // Transitorio 4% + "07" => 0.08, // Tarifa transitoria 8% + "08" => 0.13, // Tarifa general 13% + "09" => 0.05, // Tarifa reducida 0.5% + "10" => 0, // Tarifa Exenta + "11" => 0 // Tarifa 0% sin derecho a crédito + ]; + $calculados[$l]['tarifa'] = $impuestoPorTarifa[$i->codigoTarifa] * 100; + $calculados[$l]['monto'] = round(floatval($calculados[$l]['baseImponible']) * $impuestoPorTarifa[$i->codigoTarifa], DECIMALES); + } + + if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { + $xmlString .= '' . $i->codigoTarifa . ''; + } + + $xmlString .= '' . $calculados[$l]['tarifa'] . ''; + + if (isset($i->factorIVA) && $i->factorIVA != "") { + $xmlString .= '' . $i->factorIVA . ''; + } + + if ( + isset($i->codigo) && + in_array($i->codigo, ["03", "04", "05", "06"]) && + isset($i->datosImpuestoEspecifico) && + is_object($i->datosImpuestoEspecifico) + ) { + $datosImpuestoEsp = $i->datosImpuestoEspecifico; + $xmlString .= ''; + if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { + $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; + } + if (isset($datosImpuestoEsp->porcentaje)) { + $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; + } + if (isset($datosImpuestoEsp->proporcion)) { + $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; + } + if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { + $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; + } + if (isset($datosImpuestoEsp->impuestoUnidad)) { + $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; + } + $xmlString .= ''; + } + + if (!isset($i->monto)) { + $i->{"monto"} = $calculados[$l]['monto']; + } else { + if ($i->monto != $calculados[$l]['monto']) { + tools_reply("El monto del impuesto no coincide con el calculado para la línea $l, Calculado: " . $calculados[$l]['monto'], true); + } + } + + $xmlString .= '' . $i->monto . ''; + + if (isset($i->exoneracion) && $i->exoneracion != "") { + $xmlString .= ' + + ' . $i->exoneracion->tipoDocumento . ''; + if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { + $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; + } + $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; + if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { + $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; + } + if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { + $xmlString .= '' . $i->exoneracion->numeroInciso . ''; + } + $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; + if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { + $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; + } + $xmlString .= ' + ' . $i->exoneracion->fechaEmision . ' + ' . $i->exoneracion->tarifaExoneracion . ' + ' . $i->exoneracion->montoExoneracion . ' + '; + } + + $xmlString .= ''; + + if (isset($i->codigo) && isset($i->monto)) { + // Se suma el impuesto neto a totalImpuestos + if (!isset($calculados[$l]['impuestoNeto'])) { + $calculados[$l]['impuestoNeto'] = 0; + } + $calculados[$l]['impuestoNeto'] += round($i->monto, DECIMALES); + + if ($i->codigoTarifa === "10" || $i->codigoTarifa === "11") { + // Exento + $resumenKeys = [ + "servicio" => "totalServExentos", + "mercancia" => "totalMercanciasExentas", + ]; + $calculados['totalExento'] += ($calculados[$l]['montoTotal']); + } else { + // Gravado + $resumenKeys = [ + "servicio" => "totalServGravados", + "mercancia" => "totalMercanciasGravadas", + ]; + $calculados['totalGravado'] += ($calculados[$l]['montoTotal']); + } + + // Se suma el impuesto neto a totalImpuestos + // 4.4 se basa en los cabys para saber que es un servicio o mercaderia, + // los codigos que empiezan de 0 a 4 son mercaderias y los que empiezan de 5 a 9 son servicios + if (isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5', '6', '7', '8', '9'], true)) { + // Servicio + $calculados[$resumenKeys['servicio']] += ($calculados[$l]['montoTotal']); + } else { + // Mercancia + $calculados[$resumenKeys['mercancia']] += ($calculados[$l]['montoTotal']); + } + + $calculados['totalImpuesto'] += $i->monto; + + // Desglose de impuestos por código y tarifa + $tdiKey = $i->codigo . "-" . $i->codigoTarifa; + + if (!isset($calculados['totalDesgloseImpuesto'][$tdiKey])) { + $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ + "Codigo" => $i->codigo, + "CodigoTarifaIVA" => $i->codigoTarifa, + "TotalMontoImpuesto" => $i->monto + ]; + } else { + $_totalMontoImpuesto = $calculados['totalDesgloseImpuesto'][$tdiKey]->TotalMontoImpuesto; + $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ + "Codigo" => $i->codigo, + "CodigoTarifaIVA" => $i->codigoTarifa, + "TotalMontoImpuesto" => $i->monto + $_totalMontoImpuesto + ]; + } + } + } + } + + if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { + $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; + } else { + $xmlString .= '0'; + } + + $xmlString .= '' . $calculados[$l]['impuestoNeto'] . ''; + + // Se calcula de la siguiente manera: se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. + $calculados[$l]['montoTotalLinea'] = $calculados[$l]['subTotal'] + $calculados[$l]['impuestoNeto']; + + $xmlString .= '' . $calculados[$l]['montoTotalLinea'] . ''; + $xmlString .= ''; + + validarLinea($l, $d); + + $l++; + } + + $xmlString .= ''; + + //OtrosCargos + if (isset($otrosCargos) && $otrosCargos != "") { + foreach ($otrosCargos as $o) { + $xmlString .= ' + + ' . $o->tipoDocumentoOC . ''; + if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { + $xmlString .= ' + ' . $o->tipoDocumentoOTROS . ''; + } + if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { + $xmlString .= ' + + ' . $o->tipoIdentidadTercero . ' + ' . $o->numeroIdentidadTercero . ' + '; + } + if (isset($o->nombreTercero) && $o->nombreTercero != "") { + $xmlString .= ' + ' . $o->nombreTercero . ''; + } + $o->detalle = limpiarTexto($o->detalle, 150); + $xmlString .= ' + ' . $o->detalle . ''; + if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { + $xmlString .= ' + ' . $o->porcentajeOC . ''; + } + $xmlString .= ' + ' . $o->montoCargo . ''; + $xmlString .= ' + '; + } + } + + foreach ($calculados as $key => $value) { + if (is_numeric($value)) { + $calculados[$key] = round($value, DECIMALES); + } + } + + $xmlString .= ' + '; + + $xmlString .= ' + ' . $codMoneda . ' + ' . $tipoCambio . ' + '; + + // Se obtiene de la suma de los campos "Total servicios No Sujetos de IVA" mas "Total mercancías No Sujetas de IVA". + // @TODO revisar no sujetos + // $calculados['totalNoSujeto'] = $calculados['totalServNoSujetos'] + $calculados['totalMercanciasNoSujetas']; + + // Se obtiene de la suma de los campos "total servicios exonerados de IVA" mas "total de mercancías exoneradas del IVA". + // @TODO revisar exoneraciones + // $calculados['totalExonerado'] = $calculados['totalServExonerados'] + $calculados['totalMercanciasExoneradas']; + + // Calcular totalVenta + // Se obtiene de la sumatoria de los campos “total gravado”, “total exento”, “Total Exonerado” y “Total No Sujeto + $calculados['totalVenta'] = + $calculados['totalGravado'] + + $calculados['totalExento'] + + $calculados['totalExonerado'] + + $calculados['totalNoSujeto']; + + // Calcular totalVentaNeta + // Se obtiene de la resta de los campos total venta menos total descuento + $calculados['totalVentaNeta'] = $calculados['totalVenta'] - $calculados['totalDescuentos']; + + // totalComprobante: Se obtiene de la suma de los campos "total venta neta", "monto total del impuesto" y "total otros cargos" menos "total IVA devuelto", en caso de contar con dichos campos. + $calculados['totalComprobante'] = $calculados['totalVentaNeta'] + $calculados['totalImpuesto']; + if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { + $calculados['totalComprobante'] += $totalOtrosCargos; + } + if ($totalIVADevuelto != '') { + $calculados['totalComprobante'] -= $totalIVADevuelto; + } + + // agrega los campos del resumen que esten en el array calculados y no esten vacios + foreach (AUTO_CALCULAR as $campoResumen) { + if ($campoResumen == "totalDesgloseImpuesto") { + // Add logic for TotalDesgloseImpuesto + $totalDesgloseImpuesto = $calculados['totalDesgloseImpuesto'] ?? []; + if (count($totalDesgloseImpuesto) > 0) { + foreach ($totalDesgloseImpuesto as $impuesto) { + $xmlString .= ' + '; + if (isset($impuesto->Codigo)) { + $xmlString .= '' . $impuesto->Codigo . ''; + } + if (isset($impuesto->CodigoTarifaIVA)) { + $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; + } + if (isset($impuesto->TotalMontoImpuesto)) { + $xmlString .= '' . round($impuesto->TotalMontoImpuesto, DECIMALES) . ''; + } + $xmlString .= ''; + } + } + continue; + } + + if ($calculados[$campoResumen] != '') { + $xmlString .= '<' . ucfirst($campoResumen) . '>' . $calculados[$campoResumen] . ''; + } + } + + if ($totalImpAsumidoEmisorFabrica != '') { + $xmlString .= ' + ' . $totalImpAsumidoEmisorFabrica . ''; + } + + if ($totalIVADevuelto != '') { + $xmlString .= ' + ' . $totalIVADevuelto . ''; + } + + if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { + $xmlString .= ' + ' . $totalOtrosCargos . ''; + } + + if (isset($mediosPago) && !empty($mediosPago)) { + $totalMediosPago = 0; + foreach ($mediosPago as $o) { + $xmlString .= ' + '; + + // Add TipoMedioPago + if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { + $xmlString .= '' . $o->tipoMedioPago . ''; + } + + // Add MedioPagoOtros (only if TipoMedioPago is "99") + if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { + $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; + } + + // Add TotalMedioPago + if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { + $xmlString .= '' . round($o->totalMedioPago, DECIMALES) . ''; + } + + $xmlString .= ''; + $totalMediosPago += floatval($o->totalMedioPago); + } + + $t1 = round((float) $totalMediosPago, 2); + $t2 = round((float) $calculados['totalComprobante'], 2); + if (abs($t1 - $t2) > 1) { + tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [$t1], Total Comprobante: [$t2]", true); + } + } + + $xmlString .= '' . $calculados['totalComprobante'] . ''; + + $xmlString .= ' '; + + if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { + foreach ($informacionReferencia as $ref) { + if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { + if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { + $xmlString .= ''; + $xmlString .= '' . $ref->tipoDoc . ''; + if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { + $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; + } + if (isset($ref->numero)) { + $xmlString .= '' . $ref->numero . ''; + } + $xmlString .= '' . $ref->fechaEmision . ''; + if (isset($ref->codigo)) { + $xmlString .= '' . $ref->codigo . ''; + if ($ref->codigo === '99' && isset($ref->codigoOtro)) { + $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; + } + } + if (isset($ref->razon)) { + $xmlString .= '' . $ref->razon . ''; + } + $xmlString .= ''; + } else { + grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); + } + } + } + } + + // JSON de ejemplo + // { + // "otroTexto": { + // "codigo": "COD1", + // "texto": "Texto opcional 1" + // }, + // "otroContenido": [ + // { + // "codigo": "CONT1", + // "contenidoEstructurado": { + // "ContactoDesarrollador": { + // "Correo": "developer@example.com", + // "Nombre": "Developer Name", + // "Telefono": "+123456789" + // } + // } + // }, + // { + // "codigo": "CONT2", + // "contenidoEstructurado": { + // "SoporteTecnico": { + // "Correo": "support@example.com", + // "Nombre": "Support Team", + // "Telefono": "+987654321" + // } + // } + // } + // ] + //} + + // Start Otros element + $xmlString .= ''; + + // Handle multiple OtroTexto elements + if (isset($otros->otroTexto)) { + if (is_array($otros->otroTexto)) { + foreach ($otros->otroTexto as $otroTexto) { + $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; + $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } else { + $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; + $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } + + // Handle multiple OtroContenido elements + if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { + foreach ($otros->otroContenido as $otroContenido) { + $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; + // Serialize structured content as JSON string, or use a plain string + $contenido = ''; + if (isset($otroContenido->contenidoEstructurado)) { + // Convert object/array to JSON string + $contenido = htmlspecialchars(json_encode($otroContenido->contenidoEstructurado, JSON_UNESCAPED_UNICODE)); + } + $xmlString .= '' . $contenido . ''; + } + } + + $xmlString .= ''; + + // XML Resultante + // + // Texto opcional 1 + // + // + // developer@example.com + // Developer Name + // +123456789 + // + // + // + // + // support@example.com + // Support Team + // +987654321 + // + // + // + + // Cierre del XML + $xmlString .= $xmlClosingTag; + + // eliminar espacios en blanco para reducir el tamaño del XML + $xmlString = preg_replace('/>\s+<', $xmlString); + + $validacion = ValidadorXML::validateXml($xmlString, $consecutivo); + + if ($validacion && $validacion->status == "error") { + if (conf_get('mode', 'core', 'web') == 'cli') { + return tools_reply([ + "Status" => 400, + "text" => array( + "clave" => $clave, + "validacion" => $validacion + ) + ], true); + } + + tools_reply([ + "Status" => 400, + "text" => array( + "clave" => $clave, + "validacion" => $validacion + ) + ], true); + } + + return array( + "clave" => $clave, + "xml" => base64_encode($xmlString) + ); +} + +/* * ************************************************** */ +/* Funcion de prueba */ +/* * ************************************************** */ + +function test() +{ + return "Esto es un test"; +} + + + +function old_genXMLNC() +{ + + // Datos contribuyente + $clave = params_get("clave"); + $proveedorSistemas = params_get("proveedor_sistemas"); + $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json + $codigoActividadReceptor = params_get("codigo_actividad_receptor"); + $consecutivo = params_get("consecutivo"); + $fechaEmision = params_get("fecha_emision"); + + // Datos emisor + $emisorNombre = params_get("emisor_nombre"); + $emisorTipoIdentif = params_get("emisor_tipo_identif"); + $emisorNumIdentif = params_get("emisor_num_identif"); + $emisorNombreComercial = params_get("emisor_nombre_comercial"); + $emisorProv = params_get("emisor_provincia"); + $emisorCanton = params_get("emisor_canton"); + $emisorDistrito = params_get("emisor_distrito"); + $emisorBarrio = params_get("emisor_barrio"); + $emisorOtrasSenas = params_get("emisor_otras_senas"); + $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); + $emisorTel = params_get("emisor_tel"); + $emisorEmail = params_get("emisor_email"); + $registroFiscal8707 = params_get("registrofiscal8707"); + + // Datos receptor + $omitir_receptor = params_get("omitir_receptor"); // Deprecated + $receptorNombre = params_get("receptor_nombre"); + $receptorTipoIdentif = params_get("receptor_tipo_identif"); + $receptorNumIdentif = params_get("receptor_num_identif"); + $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); + $receptorNombreComercial = params_get("receptor_nombre_comercial"); + $receptorProvincia = params_get("receptor_provincia"); + $receptorCanton = params_get("receptor_canton"); + $receptorDistrito = params_get("receptor_distrito"); + $receptorBarrio = params_get("receptor_barrio"); + $receptorOtrasSenas = params_get("receptor_otras_senas"); + $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); + $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); + $receptorTel = params_get("receptor_tel"); + $receptorEmail = params_get("receptor_email"); + + // Detalles de tiquete / Factura + $condVenta = params_get("condicion_venta"); + $condVentaOtros = params_get("condicion_venta_otros"); + $plazoCredito = params_get("plazo_credito"); + $codMoneda = params_get("cod_moneda"); + $tipoCambio = params_get("tipo_cambio"); + + $totalServGravados = params_get("total_serv_gravados"); + $totalServExentos = params_get("total_serv_exentos"); + $totalServExonerado = params_get("total_serv_exonerados"); + $totalServNoSujeto = params_get("total_serv_no_sujeto"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); + $totalMercExonerada = params_get("total_merc_exonerada"); + $totalMercNoSujeta = params_get("total_merc_no_sujeta"); + $totalGravado = params_get("total_gravado"); + $totalExento = params_get("total_exento"); + $totalExonerado = params_get("total_exonerado"); + $totalNoSujeto = params_get("total_no_sujeto"); + $totalVenta = params_get("total_venta"); + $totalDescuentos = params_get("total_descuentos"); + $totalVentasNeta = params_get("total_ventas_neta"); + $totalImp = params_get("total_impuestos"); + $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); + $totalIVADevuelto = params_get("totalIVADevuelto"); + $totalOtrosCargos = params_get("totalOtrosCargos"); + // $totalComprobante = params_get("total_comprobante"); + + $otros = json_decode(params_get('otros')); + + // Detalles de la compra + $detalles = json_decode(params_get("detalles")); + $informacionReferencia = json_decode(params_get("informacion_referencia")); + $otrosCargos = json_decode(params_get("otrosCargos")); + $mediosPago = json_decode(params_get("medios_pago")); + // Resumen + $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); + + if (isset($otrosCargos) && $otrosCargos != "") { + grace_debug(params_get("otrosCargos")); + } + + if (isset($mediosPago) && $mediosPago != "") { + grace_debug(params_get("medios_pago")); + } + + if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { + grace_debug(params_get("totalDesgloseImpuesto")); + } + + // Validate string sizes + $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); + } + + if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { + error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); + } + + if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { + error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); + } + + if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { + error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); + } + + if (isset($otrosCargos) && $otrosCargos != "") { + if (count($otrosCargos) > 15) { + error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); + //Delimita el array a solo 15 elementos + $otrosCargos = array_slice($otrosCargos, 0, 15); + } + } + + if (isset($mediosPago) && $mediosPago != "") { + if (count($mediosPago) > 4) { + error_log("otrosCargos: " . count($mediosPago) . " is greater than 4"); + //Delimita el array a solo 4 elementos + $mediosPago = array_slice($mediosPago, 0, 4); + } + } + + $xmlString = ' + + ' . $clave . ' + ' . $proveedorSistemas . ' + ' . $codigoActividadEmisor . ''; + + if (isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { + $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); + } + + $xmlString .= ' + ' . $codigoActividadReceptor . ''; + } + + $xmlString .= ' + ' . $consecutivo . ' + ' . $fechaEmision . ' + + ' . $emisorNombre . ' + + ' . $emisorTipoIdentif . ' + ' . $emisorNumIdentif . ' + '; + + if (isset($registroFiscal8707) && $registroFiscal8707 != "") { + $xmlString .= ' + ' . $registroFiscal8707 . ''; + } + + if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { + $xmlString .= ' + ' . $emisorNombreComercial . ''; + } + + if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { + $xmlString .= ' + ' . $emisorProv . ' + ' . $emisorCanton . ' + ' . $emisorDistrito . ''; + + if ($emisorBarrio != '') { + $xmlString .= '' . $emisorBarrio . ''; + } + if ($emisorOtrasSenas != '') { + $xmlString .= '' . $emisorOtrasSenas . ''; + } + $xmlString .= ''; + } + + if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { + $xmlString .= ' + + ' . $emisorCodPaisTel . ' + ' . $emisorTel . ' + '; + } + + if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { + $xmlString .= '' . trim($emisorEmail) . ''; + } else { + return tools_reply([ + "Status" => 400, + "text" => "El correo del emisor no cumple con el formato establecido." + ], true); + } + + if ($omitir_receptor != 'true') { + $xmlString .= ' + ' . $receptorNombre . ''; + + if ($receptorTipoIdentif != '' && $receptorNumIdentif != '') { + $xmlString .= ' + + ' . $receptorTipoIdentif . ' + ' . $receptorNumIdentif . ' + '; + } + + if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { + $xmlString .= ' + ' + . $receptorIdentifExtranjero . + ''; + } + + if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { + $xmlString .= ' + ' . $receptorNombreComercial . ''; + } + + if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { + $xmlString .= ' + + ' . $receptorProvincia . ' + ' . $receptorCanton . ' + ' . $receptorDistrito . ''; + if ($receptorBarrio != '') { + $xmlString .= ' + ' . $receptorBarrio . ''; + } + $xmlString .= ' + ' . $receptorOtrasSenas . ' + '; + } + + if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { + $xmlString .= ' + ' + . $receptorOtrasSenasExtranjero . + ''; + } + + if ($receptorCodPaisTel != '' && $receptorTel != '') { + $xmlString .= ' + + ' . $receptorCodPaisTel . ' + ' . $receptorTel . ' + '; + } + + if ($receptorEmail != '') { + $xmlString .= '' . $receptorEmail . ''; + } + + $xmlString .= ''; + } + + $xmlString .= ' + ' . $condVenta . ''; + + if (isset($condVentaOtros) && $condVentaOtros != "") { + $xmlString .= ' + ' . $condVentaOtros . ''; + } + + if (isset($plazoCredito) && $plazoCredito != "") { + $xmlString .= ' + ' . $plazoCredito . ''; + } + + $xmlString .= ' + '; + + /* EJEMPLO DE DETALLES + { + "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], + "2":["1","Sp","Honorarios","100000","100000","100000","100000"] + } + */ + $l = 1; + foreach ($detalles as $d) { + $xmlString .= ' + ' . $l . ''; + + if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { + $xmlString .= '' . $d->partidaArancelaria . ''; + } + + if (isset($d->codigoCABYS) && $d->codigoCABYS != "") { + $xmlString .= ' + ' . $d->codigoCABYS . ''; + } + + if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { + // Convertir el objeto $d->codigoComercial en un array + $codigoComercialArray = (array) $d->codigoComercial; + + // Delimitar el array a solo 5 elementos + if (count($codigoComercialArray) > 5) { + error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); + } + $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); + + // Iterar sobre los elementos del array + foreach ($codigoComercialArray as $codigos) { + $c = (array) $codigos; + // Verificar si el elemento es un array asociativo + if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { + $xmlString .= ' + + ' . $c['tipo'] . ' + ' . $c['codigo'] . ' + '; + } + } + } + + $xmlString .= ' + ' . $d->cantidad . ' + ' . $d->unidadMedida . ''; + if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { + $xmlString .= ' + ' . $d->tipoTransaccion . ''; + } + if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { + $xmlString .= ' + ' . $d->unidadMedidaComercial . ''; + } + $d->detalle = limpiarTexto($d->detalle, 150); + $xmlString .= ' + ' . $d->detalle . ''; + if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { + $xmlString .= '' . $d->numeroVINoSerie . ''; + } + + if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { + $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; + } + if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { + $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; + } + + if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { + $xmlString .= ''; + $lineas = array_slice($d->detalleSurtido, 0, 20); + foreach ($lineas as $linea) { + $xmlString .= ''; + $xmlString .= '' . $linea->codigoCABYSSurtido . ''; + if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { + $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); + foreach ($codigos as $codigo) { + $xmlString .= ''; + $xmlString .= '' . $codigo->tipoSurtido . ''; + $xmlString .= '' . $codigo->codigoSurtido . ''; + $xmlString .= ''; + } + } + $xmlString .= '' . $linea->cantidadSurtido . ''; + $xmlString .= '' . $linea->unidadMedidaSurtido . ''; + if (isset($linea->unidadMedidaComercialSurtido)) { + $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; + } + $xmlString .= '' . $linea->detalleSurtido . ''; + $xmlString .= '' . $linea->precioUnitarioSurtido . ''; + $xmlString .= '' . $linea->montoTotalSurtido . ''; + if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { + $descuentos = array_slice($linea->descuentoSurtido, 0, 5); + foreach ($descuentos as $desc) { + $xmlString .= ''; + $xmlString .= '' . $desc->montoDescuentoSurtido . ''; + $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; + if (isset($desc->descuentoSurtidoOtros)) { + $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; + } + $xmlString .= ''; + } + } + $xmlString .= '' . $linea->subTotalSurtido . ''; + if (isset($linea->ivaCobradoFabricaSurtido)) { + $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; + } + $xmlString .= '' . $linea->baseImponibleSurtido . ''; + if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { + $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); + foreach ($impuestos as $imp) { + $xmlString .= ''; + $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; + if (isset($imp->codigoImpuestoOTROSurtido)) { + $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; + } + if (isset($imp->codigoTarifaIVASurtido)) { + $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; + } + if (isset($imp->tarifaSurtido)) { + $xmlString .= '' . $imp->tarifaSurtido . ''; + } + if (isset($imp->datosImpuestoEspecificoSurtido)) { + $e = $imp->datosImpuestoEspecificoSurtido; + $xmlString .= ''; + if (isset($e->cantidadUnidadMedidaSurtido)) { + $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; + } + if (isset($e->porcentajeSurtido)) { + $xmlString .= '' . $e->porcentajeSurtido . ''; + } + if (isset($e->proporcionSurtido)) { + $xmlString .= '' . $e->proporcionSurtido . ''; + } + if (isset($e->volumenUnidadConsumoSurtido)) { + $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; + } + if (isset($e->impuestoUnidadSurtido)) { + $xmlString .= '' . $e->impuestoUnidadSurtido . ''; + } + $xmlString .= ''; + } + $xmlString .= '' . $imp->montoImpuestoSurtido . ''; + $xmlString .= ''; + } + } + $xmlString .= ''; + } + $xmlString .= ''; + } + + $xmlString .= ' + ' . round(floatval($d->precioUnitario), DECIMALES) . ' + ' . $d->montoTotal . ''; + + if (isset($d->descuento) && !empty($d->descuento)) { + $descuentoArray = (array) $d->descuento; + + if (count($descuentoArray) > 5) { + error_log("descuento: " . count($descuentoArray) . " is greater than 5"); + } + $descuentoArray = array_slice($descuentoArray, 0, 5); + + foreach ($descuentoArray as $descuentos) { + $c = (array) $descuentos; + if ( + is_array($c) && + isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && + isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" + ) { + $xmlString .= ' + + ' . round(floatval($c['montoDescuento']), DECIMALES) . ' + ' . $c['codigoDescuento'] . ''; + // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo + if ( + isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && + isset($c['codigoDescuentoOTRO']) && + strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 + ) { + $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; + } + // NaturalezaDescuento: minOccurs=0, longitud 3-80 + if ( + isset($c['naturalezaDescuento']) && + strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 + ) { + $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; + } + $xmlString .= ' + '; + } + } + } + + $xmlString .= '' . $d->subTotal . ''; + + if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { + $xmlString .= '' . $d->IVACobradoFabrica . ''; + } + + if (isset($d->baseImponible) && $d->baseImponible != "") { + $xmlString .= '' . $d->baseImponible . ''; + } + if (isset($d->impuesto) && $d->impuesto != "") { + foreach ($d->impuesto as $i) { + $xmlString .= ' + ' . $i->codigo . ''; + + // Add if required + if ( + isset($i->codigo) && $i->codigo == "99" && + isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) + ) { + $xmlString .= '' . $i->codigoImpuestoOtro . ''; + } + + if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { + $xmlString .= '' . $i->codigoTarifa . ''; + } + + if (isset($i->tarifa) && $i->tarifa != "") { + $xmlString .= '' . $i->tarifa . ''; + } + + if (isset($i->factorIVA) && $i->factorIVA != "") { + $xmlString .= '' . $i->factorIVA . ''; + } + + if ( + isset($i->codigo) && + in_array($i->codigo, ["03", "04", "05", "06"]) && + isset($i->datosImpuestoEspecifico) && + is_object($i->datosImpuestoEspecifico) + ) { + $datosImpuestoEsp = $i->datosImpuestoEspecifico; + $xmlString .= ''; + if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { + $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; + } + if (isset($datosImpuestoEsp->porcentaje)) { + $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; + } + if (isset($datosImpuestoEsp->proporcion)) { + $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; + } + if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { + $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; + } + if (isset($datosImpuestoEsp->impuestoUnidad)) { + $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; + } + $xmlString .= ''; + } + + $xmlString .= '' . $i->monto . ''; + + if (isset($i->montoExportacion) && $i->montoExportacion != "") { + $xmlString .= '' . $i->montoExportacion . ''; + } + + if (isset($i->exoneracion) && $i->exoneracion != "") { + $xmlString .= ' + + ' . $i->exoneracion->tipoDocumento . ''; + if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { + $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; + } + $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; + if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { + $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; + } + if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { + $xmlString .= '' . $i->exoneracion->numeroInciso . ''; + } + $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; + if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { + $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; + } + $xmlString .= ' + ' . $i->exoneracion->fechaEmision . ' + ' . $i->exoneracion->tarifaExoneracion . ' + ' . $i->exoneracion->montoExoneracion . ' + '; + } + + $xmlString .= ''; + } + } + if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { + $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; + } + $xmlString .= '' . $d->impuestoNeto . ''; + $xmlString .= '' . $d->montoTotalLinea . ''; + $xmlString .= ''; + $l++; + } + + $xmlString .= ''; + + //OtrosCargos + if (isset($otrosCargos) && $otrosCargos != "") { + foreach ($otrosCargos as $o) { + $xmlString .= ' + + ' . $o->tipoDocumentoOC . ''; + if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { + $xmlString .= ' + ' . $o->tipoDocumentoOTROS . ''; + } + if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { + $xmlString .= ' + + ' . $o->tipoIdentidadTercero . ' + ' . $o->numeroIdentidadTercero . ' + '; + } + if (isset($o->nombreTercero) && $o->nombreTercero != "") { + $xmlString .= ' + ' . $o->nombreTercero . ''; + } + $o->detalle = limpiarTexto($o->detalle, 150); + $xmlString .= ' + ' . $o->detalle . ''; + if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { + $xmlString .= ' + ' . $o->porcentajeOC . ''; + } + $xmlString .= ' + ' . $o->montoCargo . ''; + $xmlString .= ' + '; + } + } + + $xmlString .= ' + '; + + if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { + $xmlString .= ' + + ' . $codMoneda . ' + ' . $tipoCambio . ' + '; + } else { + $xmlString .= ' + + CRC + 1 + '; + } + + if ($totalServGravados != '') { + $xmlString .= ' + ' . $totalServGravados . ''; + } + + if ($totalServExentos != '') { + $xmlString .= ' + ' . $totalServExentos . ''; + } + + if ($totalServExonerado != '') { + $xmlString .= ' + ' . $totalServExonerado . ''; + } + + if ($totalServNoSujeto != '') { + $xmlString .= ' + ' . $totalServNoSujeto . ''; + } + + if ($totalMercanciasGravadas != '') { + $xmlString .= ' + ' . $totalMercanciasGravadas . ''; + } + + if ($totalMercanciasExentas != '') { + $xmlString .= ' + ' . $totalMercanciasExentas . ''; + } + + if ($totalMercExonerada != '') { + $xmlString .= ' + ' . $totalMercExonerada . ''; + } + + if ($totalMercNoSujeta != '') { + $xmlString .= ' + ' . $totalMercNoSujeta . ''; + } + + if ($totalGravado != '') { + $xmlString .= ' + ' . $totalGravado . ''; + } + + if ($totalExento != '') { + $xmlString .= ' + ' . $totalExento . ''; + } + + if ($totalExonerado != '') { + $xmlString .= ' + ' . $totalExonerado . ''; + } + + if ($totalNoSujeto != '') { + $xmlString .= ' + ' . $totalNoSujeto . ''; + } + + $xmlString .= ' + ' . $totalVenta . ''; + + if ($totalDescuentos != '') { + $xmlString .= ' + ' . $totalDescuentos . ''; + } + + $xmlString .= ' + ' . $totalVentasNeta . ''; + + // Add logic for TotalDesgloseImpuesto + if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { + foreach ($totalDesgloseImpuesto as $impuesto) { + $xmlString .= ' + '; + if (isset($impuesto->Codigo)) { + $xmlString .= '' . $impuesto->Codigo . ''; + } + if (isset($impuesto->CodigoTarifaIVA)) { + $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; + } + if (isset($impuesto->TotalMontoImpuesto)) { + $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; + } + $xmlString .= ''; + } + } + + if ($totalImp != '') { + $xmlString .= ' + ' . $totalImp . ''; + } + + if ($totalImpAsumidoEmisorFabrica != '') { + $xmlString .= ' + ' . $totalImpAsumidoEmisorFabrica . ''; + } + + if ($totalIVADevuelto != '') { + $xmlString .= ' + ' . $totalIVADevuelto . ''; + } + + if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { + $xmlString .= ' + ' . $totalOtrosCargos . ''; + } + + if (isset($mediosPago) && !empty($mediosPago)) { + foreach ($mediosPago as $o) { + $xmlString .= ' + '; + + // Add TipoMedioPago + if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { + $xmlString .= '' . $o->tipoMedioPago . ''; + } + + // Add MedioPagoOtros (only if TipoMedioPago is "99") + if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { + $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; + } + + // Add TotalMedioPago + if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { + $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; + } + + $xmlString .= ''; + } + } + + $xmlString .= ' + ' . $totalComprobante . ' + '; + + if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { + foreach ($informacionReferencia as $ref) { + if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { + if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { + $xmlString .= ''; + $xmlString .= '' . $ref->tipoDoc . ''; + if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { + $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; + } + if (isset($ref->numero)) { + $xmlString .= '' . $ref->numero . ''; + } + $xmlString .= '' . $ref->fechaEmision . ''; + if (isset($ref->codigo)) { + $xmlString .= '' . $ref->codigo . ''; + if ($ref->codigo === '99' && isset($ref->codigoOtro)) { + $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; + } + } + if (isset($ref->razon)) { + $xmlString .= '' . $ref->razon . ''; + } + $xmlString .= ''; + } else { + grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); + } + } + } + } + + // JSON de ejemplo + // { + // "otroTexto": { + // "codigo": "COD1", + // "texto": "Texto opcional 1" + // }, + // "otroContenido": [ + // { + // "codigo": "CONT1", + // "contenidoEstructurado": { + // "ContactoDesarrollador": { + // "Correo": "developer@example.com", + // "Nombre": "Developer Name", + // "Telefono": "+123456789" + // } + // } + // }, + // { + // "codigo": "CONT2", + // "contenidoEstructurado": { + // "SoporteTecnico": { + // "Correo": "support@example.com", + // "Nombre": "Support Team", + // "Telefono": "+987654321" + // } + // } + // } + // ] + //} + + // Start Otros element + $xmlString .= ''; + + // Handle multiple OtroTexto elements + if (isset($otros->otroTexto)) { + if (is_array($otros->otroTexto)) { + foreach ($otros->otroTexto as $otroTexto) { + $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; + $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } else { + $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; + $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } + + // Handle multiple OtroContenido elements + if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { + foreach ($otros->otroContenido as $otroContenido) { + $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; + $contenido = ''; + if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { + foreach ($otroContenido->contenidoEstructurado as $tag => $data) { + $contenido .= '<' . $tag . '>'; + if (is_object($data)) { + foreach ($data as $k => $v) { + $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; + } + } + $contenido .= ''; + } + } + $xmlString .= '' . $contenido . ''; + } + } + + $xmlString .= ''; + + // XML Resultante + // + // Texto opcional 1 + // + // + // developer@example.com + // Developer Name + // +123456789 + // + // + // + // + // support@example.com + // Support Team + // +987654321 + // + // + // + + $xmlString .= ' + '; + + return array( + "clave" => $clave, + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) + ); +} + +function old_genXMLND() +{ + + // Datos contribuyente + $clave = params_get("clave"); + $proveedorSistemas = params_get("proveedor_sistemas"); + $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json + $codigoActividadReceptor = params_get("codigo_actividad_receptor"); + $consecutivo = params_get("consecutivo"); + $fechaEmision = params_get("fecha_emision"); + + // Datos emisor + $emisorNombre = params_get("emisor_nombre"); + $emisorTipoIdentif = params_get("emisor_tipo_identif"); + $emisorNumIdentif = params_get("emisor_num_identif"); + $emisorNombreComercial = params_get("emisor_nombre_comercial"); + $emisorProv = params_get("emisor_provincia"); + $emisorCanton = params_get("emisor_canton"); + $emisorDistrito = params_get("emisor_distrito"); + $emisorBarrio = params_get("emisor_barrio"); + $emisorOtrasSenas = params_get("emisor_otras_senas"); + $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); + $emisorTel = params_get("emisor_tel"); + $emisorEmail = params_get("emisor_email"); + $registroFiscal8707 = params_get("registrofiscal8707"); + + // Datos receptor + $omitir_receptor = params_get("omitir_receptor"); // Deprecated + $receptorNombre = params_get("receptor_nombre"); + $receptorTipoIdentif = params_get("receptor_tipo_identif"); + $receptorNumIdentif = params_get("receptor_num_identif"); + $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); + $receptorNombreComercial = params_get("receptor_nombre_comercial"); + $receptorProvincia = params_get("receptor_provincia"); + $receptorCanton = params_get("receptor_canton"); + $receptorDistrito = params_get("receptor_distrito"); + $receptorBarrio = params_get("receptor_barrio"); + $receptorOtrasSenas = params_get("receptor_otras_senas"); + $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); + $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); + $receptorTel = params_get("receptor_tel"); + $receptorEmail = params_get("receptor_email"); + + // Detalles de tiquete / Factura + $condVenta = params_get("condicion_venta"); + $condVentaOtros = params_get("condicion_venta_otros"); + $plazoCredito = params_get("plazo_credito"); + $codMoneda = params_get("cod_moneda"); + $tipoCambio = params_get("tipo_cambio"); + $totalServGravados = params_get("total_serv_gravados"); + $totalServExentos = params_get("total_serv_exentos"); + $totalServExonerado = params_get("total_serv_exonerados"); + $totalServNoSujeto = params_get("total_serv_no_sujeto"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); + $totalMercExonerada = params_get("total_merc_exonerada"); + $totalMercNoSujeta = params_get("total_merc_no_sujeta"); + $totalGravado = params_get("total_gravados"); + $totalExento = params_get("total_exento"); + $totalExonerado = params_get("total_exonerado"); + $totalNoSujeto = params_get("total_no_sujeto"); + $totalVenta = params_get("total_ventas"); + $totalDescuentos = params_get("total_descuentos"); + $totalVentasNeta = params_get("total_ventas_neta"); + $totalImp = params_get("total_impuestos"); + $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); + $totalIVADevuelto = params_get("totalIVADevuelto"); + $totalOtrosCargos = params_get("totalOtrosCargos"); + $totalComprobante = params_get("total_comprobante"); + $otros = json_decode(params_get('otros')); + + // Detalles de la compra + $detalles = json_decode(params_get("detalles")); + $informacionReferencia = json_decode(params_get("informacion_referencia")); + $otrosCargos = json_decode(params_get("otrosCargos")); + $mediosPago = json_decode(params_get("medios_pago")); + // Resumen + $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); + + if (isset($otrosCargos) && $otrosCargos != "") { + grace_debug(params_get("otrosCargos")); + } + + if (isset($mediosPago) && $mediosPago != "") { + grace_debug(params_get("medios_pago")); + } + + if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { + grace_debug(params_get("totalDesgloseImpuesto")); + } + + // Validate string sizes + $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); + } + + if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { + error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); + } + + if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { + error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); + } + + if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { + error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); + } + + if (isset($otrosCargos) && $otrosCargos != "") { + if (count($otrosCargos) > 15) { + error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); + //Delimita el array a solo 15 elementos + $otrosCargos = array_slice($otrosCargos, 0, 15); + } + } + + if (isset($mediosPago) && $mediosPago != "") { + if (count($mediosPago) > 4) { + error_log("medios_pago: " . count($mediosPago) . " is greater than 4"); + //Delimita el array a solo 4 elementos + $mediosPago = array_slice($mediosPago, 0, 4); + } + } + + $xmlString = ' + + ' . $clave . ' + ' . $proveedorSistemas . ' + ' . $codigoActividadEmisor . ''; + + if (isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { + $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); + } + + $xmlString .= ' + ' . $codigoActividadReceptor . ''; + } + + $xmlString .= ' + ' . $consecutivo . ' + ' . $fechaEmision . ' + + ' . $emisorNombre . ' + + ' . $emisorTipoIdentif . ' + ' . $emisorNumIdentif . ' + '; + + if (isset($registroFiscal8707) && $registroFiscal8707 != "") { + $xmlString .= ' + ' . $registroFiscal8707 . ''; + } + + if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { + $xmlString .= ' + ' . $emisorNombreComercial . ''; + } + + if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { + $xmlString .= ' + + ' . $emisorProv . ' + ' . $emisorCanton . ' + ' . $emisorDistrito . ''; + if ($emisorBarrio != '') { + $xmlString .= '' . $emisorBarrio . ''; + } + $xmlString .= ' + ' . $emisorOtrasSenas . ' + '; + } + + if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { + $xmlString .= ' + + ' . $emisorCodPaisTel . ' + ' . $emisorTel . ' + '; + } + + if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { + $xmlString .= '' . trim($emisorEmail) . ''; + } else { + error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); + } + + if ($omitir_receptor != 'true') { + $xmlString .= ' + ' . $receptorNombre . ''; + + if ($receptorTipoIdentif != '' && $receptorNumIdentif != '') { + $xmlString .= ' + ' . $receptorTipoIdentif . ' + ' . $receptorNumIdentif . ' + '; + } + + if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { + $xmlString .= ' + ' + . $receptorIdentifExtranjero . + ''; + } + + if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { + $xmlString .= ' + ' . $receptorNombreComercial . ''; + } + + if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { + $xmlString .= ' + + ' . $receptorProvincia . ' + ' . $receptorCanton . ' + ' . $receptorDistrito . ''; + if ($receptorBarrio != '') { + $xmlString .= '' . $receptorBarrio . ''; + } + $xmlString .= ' + ' . $receptorOtrasSenas . ' + '; + } + + if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { + $xmlString .= ' + ' + . $receptorOtrasSenasExtranjero . + ''; + } + + if ($receptorCodPaisTel != '' && $receptorTel != '') { + $xmlString .= ' + + ' . $receptorCodPaisTel . ' + ' . $receptorTel . ' + '; + } + + if ($receptorEmail != '') { + $xmlString .= '' . $receptorEmail . ''; + } + + $xmlString .= ''; + } + + $xmlString .= ' + ' . $condVenta . ''; + + if (isset($condVentaOtros) && $condVentaOtros != "") { + $xmlString .= ' + ' . $condVentaOtros . ''; + } + + if (isset($plazoCredito) && $plazoCredito != "") { + $xmlString .= ' + ' . $plazoCredito . ''; + } + + $xmlString .= ' + '; + + /* EJEMPLO DE DETALLES + { + "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], + "2":["1","Sp","Honorarios","100000","100000","100000","100000"] + } + */ + + $l = 1; + foreach ($detalles as $d) { + $xmlString .= ' + + ' . $l . ''; + if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { + $xmlString .= '' . $d->partidaArancelaria . ''; + } + + if (isset($d->codigoCABYS) && $d->codigoCABYS != "") { + $xmlString .= ' + ' . $d->codigoCABYS . ''; + } + + if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { + // Convertir el objeto $d->codigoComercial en un array + $codigoComercialArray = (array) $d->codigoComercial; + + // Delimitar el array a solo 5 elementos + if (count($codigoComercialArray) > 5) { + error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); + } + $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); + + // Iterar sobre los elementos del array + foreach ($codigoComercialArray as $codigos) { + $c = (array) $codigos; + // Verificar si el elemento es un array asociativo + if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { + $xmlString .= ' + + ' . $c['tipo'] . ' + ' . $c['codigo'] . ' + '; + } + } + } + + $xmlString .= ' + ' . $d->cantidad . ' + ' . $d->unidadMedida . ''; + if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { + $xmlString .= ' + ' . $d->tipoTransaccion . ''; + } + if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { + $xmlString .= ' + ' . $d->unidadMedidaComercial . ''; + } + $d->detalle = limpiarTexto($d->detalle, 150); + $xmlString .= ' + ' . $d->detalle . ''; + if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { + $xmlString .= '' . $d->numeroVINoSerie . ''; + } + + if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { + $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; + } + if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { + $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; + } + + if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { + $xmlString .= ''; + $lineas = array_slice($d->detalleSurtido, 0, 20); + foreach ($lineas as $linea) { + $xmlString .= ''; + $xmlString .= '' . $linea->codigoCABYSSurtido . ''; + if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { + $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); + foreach ($codigos as $codigo) { + $xmlString .= ''; + $xmlString .= '' . $codigo->tipoSurtido . ''; + $xmlString .= '' . $codigo->codigoSurtido . ''; + $xmlString .= ''; + } + } + $xmlString .= '' . $linea->cantidadSurtido . ''; + $xmlString .= '' . $linea->unidadMedidaSurtido . ''; + if (isset($linea->unidadMedidaComercialSurtido)) { + $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; + } + $xmlString .= '' . $linea->detalleSurtido . ''; + $xmlString .= '' . $linea->precioUnitarioSurtido . ''; + $xmlString .= '' . $linea->montoTotalSurtido . ''; + if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { + $descuentos = array_slice($linea->descuentoSurtido, 0, 5); + foreach ($descuentos as $desc) { + $xmlString .= ''; + $xmlString .= '' . $desc->montoDescuentoSurtido . ''; + $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; + if (isset($desc->descuentoSurtidoOtros)) { + $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; + } + $xmlString .= ''; + } + } + $xmlString .= '' . $linea->subTotalSurtido . ''; + if (isset($linea->ivaCobradoFabricaSurtido)) { + $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; + } + $xmlString .= '' . $linea->baseImponibleSurtido . ''; + if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { + $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); + foreach ($impuestos as $imp) { + $xmlString .= ''; + $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; + if (isset($imp->codigoImpuestoOTROSurtido)) { + $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; + } + if (isset($imp->codigoTarifaIVASurtido)) { + $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; + } + if (isset($imp->tarifaSurtido)) { + $xmlString .= '' . $imp->tarifaSurtido . ''; + } + if (isset($imp->datosImpuestoEspecificoSurtido)) { + $e = $imp->datosImpuestoEspecificoSurtido; + $xmlString .= ''; + if (isset($e->cantidadUnidadMedidaSurtido)) { + $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; + } + if (isset($e->porcentajeSurtido)) { + $xmlString .= '' . $e->porcentajeSurtido . ''; + } + if (isset($e->proporcionSurtido)) { + $xmlString .= '' . $e->proporcionSurtido . ''; + } + if (isset($e->volumenUnidadConsumoSurtido)) { + $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; + } + if (isset($e->impuestoUnidadSurtido)) { + $xmlString .= '' . $e->impuestoUnidadSurtido . ''; + } + $xmlString .= ''; + } + $xmlString .= '' . $imp->montoImpuestoSurtido . ''; + $xmlString .= ''; + } + } + $xmlString .= ''; + } + $xmlString .= ''; + } + + $xmlString .= ' + ' . $d->precioUnitario . ' + ' . $d->montoTotal . ''; + + if (isset($d->descuento) && !empty($d->descuento)) { + $descuentoArray = (array) $d->descuento; + + if (count($descuentoArray) > 5) { + error_log("descuento: " . count($descuentoArray) . " is greater than 5"); + } + $descuentoArray = array_slice($descuentoArray, 0, 5); + + foreach ($descuentoArray as $descuentos) { + $c = (array) $descuentos; + if ( + is_array($c) && + isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && + isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" + ) { + $xmlString .= ' + + ' . $c['montoDescuento'] . ' + ' . $c['codigoDescuento'] . ''; + // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo + if ( + isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && + isset($c['codigoDescuentoOTRO']) && + strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 + ) { + $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; + } + // NaturalezaDescuento: minOccurs=0, longitud 3-80 + if ( + isset($c['naturalezaDescuento']) && + strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 + ) { + $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; + } + $xmlString .= ' + '; + } + } + } + + $xmlString .= '' . $d->subTotal . ''; + + if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { + $xmlString .= '' . $d->IVACobradoFabrica . ''; + } + + if (isset($d->baseImponible) && $d->baseImponible != "") { + $xmlString .= '' . $d->baseImponible . ''; + } + if (isset($d->impuesto) && $d->impuesto != "") { + foreach ($d->impuesto as $i) { + $xmlString .= ' + ' . $i->codigo . ''; + + // Add if required + if ( + isset($i->codigo) && $i->codigo == "99" && + isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) + ) { + $xmlString .= '' . $i->codigoImpuestoOtro . ''; + } + + if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { + $xmlString .= '' . $i->codigoTarifa . ''; + } + + if (isset($i->tarifa) && $i->tarifa != "") { + $xmlString .= '' . $i->tarifa . ''; + } + + if (isset($i->factorIVA) && $i->factorIVA != "") { + $xmlString .= '' . $i->factorIVA . ''; + } + + if ( + isset($i->codigo) && + in_array($i->codigo, ["03", "04", "05", "06"]) && + isset($i->datosImpuestoEspecifico) && + is_object($i->datosImpuestoEspecifico) + ) { + $datosImpuestoEsp = $i->datosImpuestoEspecifico; + $xmlString .= ''; + if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { + $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; + } + if (isset($datosImpuestoEsp->porcentaje)) { + $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; + } + if (isset($datosImpuestoEsp->proporcion)) { + $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; + } + if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { + $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; + } + if (isset($datosImpuestoEsp->impuestoUnidad)) { + $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; + } + $xmlString .= ''; + } + + $xmlString .= '' . $i->monto . ''; + + if (isset($i->montoExportacion) && $i->montoExportacion != "") { + $xmlString .= '' . $i->montoExportacion . ''; + } + + if (isset($i->exoneracion) && $i->exoneracion != "") { + $xmlString .= ' + + ' . $i->exoneracion->tipoDocumento . ''; + if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { + $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; + } + $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; + if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { + $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; + } + if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { + $xmlString .= '' . $i->exoneracion->numeroInciso . ''; + } + $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; + if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { + $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; + } + $xmlString .= ' + ' . $i->exoneracion->fechaEmision . ' + ' . $i->exoneracion->tarifaExoneracion . ' + ' . $i->exoneracion->montoExoneracion . ' + '; + } + + $xmlString .= ''; + } + } + if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { + $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; + } + $xmlString .= '' . $d->impuestoNeto . ''; + $xmlString .= '' . $d->montoTotalLinea . ''; + $xmlString .= ''; + $l++; + } + + $xmlString .= ''; + + //OtrosCargos + if (isset($otrosCargos) && $otrosCargos != "") { + foreach ($otrosCargos as $o) { + $xmlString .= ' + + ' . $o->tipoDocumentoOC . ''; + if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { + $xmlString .= ' + ' . $o->tipoDocumentoOTROS . ''; + } + if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { + $xmlString .= ' + + ' . $o->tipoIdentidadTercero . ' + ' . $o->numeroIdentidadTercero . ' + '; + } + if (isset($o->nombreTercero) && $o->nombreTercero != "") { + $xmlString .= ' + ' . $o->nombreTercero . ''; + } + $o->detalle = limpiarTexto($o->detalle, 150); + $xmlString .= ' + ' . $o->detalle . ''; + if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { + $xmlString .= ' + ' . $o->porcentajeOC . ''; + } + $xmlString .= ' + ' . $o->montoCargo . ''; + $xmlString .= ' + '; + } + } + + $xmlString .= ' + '; + + if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { + $xmlString .= ' + + ' . $codMoneda . ' + ' . $tipoCambio . ' + '; + } else { + $xmlString .= ' + + CRC + 1 + '; + } + + if ($totalServGravados != '') { + $xmlString .= ' + ' . $totalServGravados . ''; + } + + if ($totalServExentos != '') { + $xmlString .= ' + ' . $totalServExentos . ''; + } + + if ($totalServExonerado != '') { + $xmlString .= ' + ' . $totalServExonerado . ''; + } + + if ($totalServNoSujeto != '') { + $xmlString .= ' + ' . $totalServNoSujeto . ''; + } + + if ($totalMercanciasGravadas != '') { + $xmlString .= ' + ' . $totalMercanciasGravadas . ''; + } + + if ($totalMercanciasExentas != '') { + $xmlString .= ' + ' . $totalMercanciasExentas . ''; + } + + if ($totalMercExonerada != '') { + $xmlString .= ' + ' . $totalMercExonerada . ''; + } + + if ($totalMercNoSujeta != '') { + $xmlString .= ' + ' . $totalMercNoSujeta . ''; + } + + if ($totalGravado != '') { + $xmlString .= ' + ' . $totalGravado . ''; + } + + if ($totalExento != '') { + $xmlString .= ' + ' . $totalExento . ''; + } + + if ($totalExonerado != '') { + $xmlString .= ' + ' . $totalExonerado . ''; + } + + if ($totalNoSujeto != '') { + $xmlString .= ' + ' . $totalNoSujeto . ''; + } + + $xmlString .= ' + ' . $totalVenta . ''; + + if ($totalDescuentos != '') { + $xmlString .= ' + ' . $totalDescuentos . ''; + } + + $xmlString .= ' + ' . $totalVentasNeta . ''; + + // Add logic for TotalDesgloseImpuesto + if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { + foreach ($totalDesgloseImpuesto as $impuesto) { + $xmlString .= ' + '; + if (isset($impuesto->Codigo)) { + $xmlString .= '' . $impuesto->Codigo . ''; + } + if (isset($impuesto->CodigoTarifaIVA)) { + $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; + } + if (isset($impuesto->TotalMontoImpuesto)) { + $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; + } + $xmlString .= ''; + } + } + + if ($totalImp != '') { + $xmlString .= ' + ' . $totalImp . ''; + } + + if ($totalImpAsumidoEmisorFabrica != '') { + $xmlString .= ' + ' . $totalImpAsumidoEmisorFabrica . ''; + } + + if ($totalIVADevuelto != '') { + $xmlString .= ' + ' . $totalIVADevuelto . ''; + } + + if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { + $xmlString .= ' + ' . $totalOtrosCargos . ''; + } + + if (isset($mediosPago) && !empty($mediosPago)) { + foreach ($mediosPago as $o) { + $xmlString .= ' + '; + + // Add TipoMedioPago + if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { + $xmlString .= '' . $o->tipoMedioPago . ''; + } + + // Add MedioPagoOtros (only if TipoMedioPago is "99") + if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { + $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; + } + + // Add TotalMedioPago + if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { + $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; + } + + $xmlString .= ''; + } + } + + $xmlString .= ' + ' . $totalComprobante . ' + '; + + if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { + foreach ($informacionReferencia as $ref) { + if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { + if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { + $xmlString .= ''; + $xmlString .= '' . $ref->tipoDoc . ''; + if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { + $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; + } + if (isset($ref->numero)) { + $xmlString .= '' . $ref->numero . ''; + } + $xmlString .= '' . $ref->fechaEmision . ''; + if (isset($ref->codigo)) { + $xmlString .= '' . $ref->codigo . ''; + if ($ref->codigo === '99' && isset($ref->codigoOtro)) { + $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; + } + } + if (isset($ref->razon)) { + $xmlString .= '' . $ref->razon . ''; + } + $xmlString .= ''; + } else { + grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); + } + } + } + } + + // JSON de ejemplo + // { + // "otroTexto": { + // "codigo": "COD1", + // "texto": "Texto opcional 1" + // }, + // "otroContenido": [ + // { + // "codigo": "CONT1", + // "contenidoEstructurado": { + // "ContactoDesarrollador": { + // "Correo": "developer@example.com", + // "Nombre": "Developer Name", + // "Telefono": "+123456789" + // } + // } + // }, + // { + // "codigo": "CONT2", + // "contenidoEstructurado": { + // "SoporteTecnico": { + // "Correo": "support@example.com", + // "Nombre": "Support Team", + // "Telefono": "+987654321" + // } + // } + // } + // ] + //} + + // Start Otros element + $xmlString .= ''; + + // Handle multiple OtroTexto elements + if (isset($otros->otroTexto)) { + if (is_array($otros->otroTexto)) { + foreach ($otros->otroTexto as $otroTexto) { + $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; + $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } else { + $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; + $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } + + // Handle multiple OtroContenido elements + if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { + foreach ($otros->otroContenido as $otroContenido) { + $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; + $contenido = ''; + if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { + foreach ($otroContenido->contenidoEstructurado as $tag => $data) { + $contenido .= '<' . $tag . '>'; + if (is_object($data)) { + foreach ($data as $k => $v) { + $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; + } + } + $contenido .= ''; + } + } + $xmlString .= '' . $contenido . ''; + } + } + + $xmlString .= ''; + + // XML Resultante + // + // Texto opcional 1 + // + // + // developer@example.com + // Developer Name + // +123456789 + // + // + // + // + // support@example.com + // Support Team + // +987654321 + // + // + // + + $xmlString .= ' + '; + + return array( + "clave" => $clave, + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) + ); +} + +function genXMLMr() +{ + + $clave = params_get("clave"); // d{50,50} + // Datos vendedor = emisor + $numeroCedulaEmisor = params_get("numero_cedula_emisor"); // d{12,12} cedula fisica,juridica,NITE,DIMEX + $numeroCedulaEmisor = str_pad($numeroCedulaEmisor, 12, "0", STR_PAD_LEFT); + + // Datos mensaje receptor + $fechaEmisionDoc = params_get("fecha_emision_doc"); // fecha de emision de la confirmacion + $mensaje = params_get("mensaje"); // 1 - Aceptado, 2 - Aceptado Parcialmente, 3 - Rechazado + $detalleMensaje = params_get("detalle_mensaje"); + $montoTotalImpuesto = params_get("monto_total_impuesto"); // d18,5 opcional /obligatorio si comprobante tenga impuesto + $codigoActividad = params_get("codigo_actividad"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json + $totalFactura = params_get("total_factura"); // d18,5 + $numeroConsecutivoReceptor = params_get("numero_consecutivo_receptor"); // d{20,20} numeracion consecutiva de los mensajes de confirmacion + + // Datos comprador = receptor + $numeroCedulaReceptor = params_get("numero_cedula_receptor"); // d{12,12}cedula fisica, juridica, NITE, DIMEX del comprador + $numeroCedulaReceptor = str_pad($numeroCedulaReceptor, 12, "0", STR_PAD_LEFT); + + // Validate string sizes + $codigoActividad = str_pad($codigoActividad, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividad) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize: " . CODIGOACTIVIDADSIZE . " is not codigoActividad: " . $codigoActividad); + } + + $xmlString = ' + + ' . $clave . ' + ' . $numeroCedulaEmisor . ' + ' . $fechaEmisionDoc . ' + ' . $mensaje . ''; + if (!empty($detalleMensaje)) { + $xmlString .= '' . $detalleMensaje . ''; + } + + if (!empty($montoTotalImpuesto)) { + $xmlString .= '' . $montoTotalImpuesto . ''; + } + $xmlString .= '' . $codigoActividad . ' + ' . $totalFactura . ' + ' . $numeroCedulaReceptor . ' + ' . $numeroConsecutivoReceptor . ''; + + $xmlString .= ''; + + return array( + "clave" => $clave, + "xml" => base64_encode($xmlString) + ); +} + +function genXMLFec() +{ + // Datos contribuyente + $clave = params_get("clave"); + $proveedorSistemas = params_get("proveedor_sistemas"); + $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json + $codigoActividadReceptor = params_get("codigo_actividad_receptor"); + $consecutivo = params_get("consecutivo"); + $fechaEmision = params_get("fecha_emision"); + + // Datos emisor + $emisorNombre = params_get("emisor_nombre"); + $emisorTipoIdentif = params_get("emisor_tipo_identif"); + $emisorNumIdentif = params_get("emisor_num_identif"); + $emisorNombreComercial = params_get("emisor_nombre_comercial"); + $emisorProv = params_get("emisor_provincia"); + $emisorCanton = params_get("emisor_canton"); + $emisorDistrito = params_get("emisor_distrito"); + $emisorBarrio = params_get("emisor_barrio"); + $emisorOtrasSenas = params_get("emisor_otras_senas"); + $emisorOtrasSenasExtranjero = params_get("emisor_otras_senas_extranjero"); + $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); + $emisorTel = params_get("emisor_tel"); + $emisorEmail = params_get("emisor_email"); + $registroFiscal8707 = params_get("registrofiscal8707"); + + // Datos receptor + // Deprecated - $omitir_receptor = params_get("omitir_receptor"); + $receptorNombre = params_get("receptor_nombre"); + $receptorTipoIdentif = params_get("receptor_tipo_identif"); + $receptorNumIdentif = params_get("receptor_num_identif"); + $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); + $receptorNombreComercial = params_get("receptor_nombre_comercial"); + $receptorProvincia = params_get("receptor_provincia"); + $receptorCanton = params_get("receptor_canton"); + $receptorDistrito = params_get("receptor_distrito"); + $receptorBarrio = params_get("receptor_barrio"); + $receptorOtrasSenas = params_get("receptor_otras_senas"); + $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); + $receptorTel = params_get("receptor_tel"); + $receptorEmail = params_get("receptor_email"); + + // Detalles de tiquete / Factura + $condVenta = params_get("condicion_venta"); + $condVentaOtros = params_get("condicion_venta_otros"); + $plazoCredito = params_get("plazo_credito"); + $codMoneda = params_get("cod_moneda"); + $tipoCambio = params_get("tipo_cambio"); + $totalServGravados = params_get("total_serv_gravados"); + $totalServExentos = params_get("total_serv_exentos"); + $totalServExonerado = params_get("total_serv_exonerados"); + $totalServNoSujeto = params_get("total_serv_no_sujeto"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); + $totalMercExonerada = params_get("total_merc_exonerada"); + $totalMercNoSujeta = params_get("total_merc_no_sujeta"); + $totalGravado = params_get("total_gravados"); + $totalExento = params_get("total_exento"); + $totalExonerado = params_get("total_exonerado"); + $totalNoSujeto = params_get("total_no_sujeto"); + $totalVenta = params_get("total_ventas"); + $totalDescuentos = params_get("total_descuentos"); + $totalVentasNeta = params_get("total_ventas_neta"); + $totalImp = params_get("total_impuestos"); + $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); + + $totalOtrosCargos = params_get("totalOtrosCargos"); + $totalComprobante = params_get("total_comprobante"); + $otros = json_decode(params_get('otros')); + + // Detalles de la compra + $detalles = json_decode(params_get("detalles")); + $informacionReferencia = json_decode(params_get("informacion_referencia")); + + $otrosCargos = json_decode(params_get("otrosCargos")); + $mediosPago = json_decode(params_get("medios_pago")); + // Resumen + $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); + + grace_debug(params_get("detalles")); + + if (isset($otrosCargos) && $otrosCargos != "") { + grace_debug(params_get("otrosCargos")); + } + + if (isset($mediosPago) && $mediosPago != "") { + grace_debug(params_get("medios_pago")); + } + + if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { + grace_debug(params_get("totalDesgloseImpuesto")); + } + + // Validate string sizes + $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); + } + + $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); + } + + if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { + error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); + } + + if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { + error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); + } + + if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { + error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); + } + + if (isset($otrosCargos) && $otrosCargos != "") { + if (count($otrosCargos) > 15) { + error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); + //Delimita el array a solo 15 elementos + $otrosCargos = array_slice($otrosCargos, 0, 15); + } + } + + if (isset($mediosPago) && $mediosPago != "") { + if (count($mediosPago) > 4) { + error_log("mediosPago: " . count($mediosPago) . " is greater than 4"); + //Delimita el array a solo 4 elementos + $mediosPago = array_slice($mediosPago, 0, 4); + } + } + + $xmlString = ' + + ' . $clave . ' + ' . $proveedorSistemas . ' + ' . $codigoActividadEmisor . ' + ' . $codigoActividadReceptor . ' + ' . $consecutivo . ' + ' . $fechaEmision . ' + + ' . $emisorNombre . ' + + ' . $emisorTipoIdentif . ' + ' . $emisorNumIdentif . ' + '; + + if (isset($registroFiscal8707) && $registroFiscal8707 != "") { + $xmlString .= ' + ' . $registroFiscal8707 . ''; + } + + if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { + $xmlString .= ' + ' . $emisorNombreComercial . ''; + } + + if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { + $xmlString .= ' + + ' . $emisorProv . ' + ' . $emisorCanton . ' + ' . $emisorDistrito . ''; + if ($emisorBarrio != '') { + $xmlString .= '' . $emisorBarrio . ''; + } + $xmlString .= ' + ' . $emisorOtrasSenas . ' + '; + } + + if ($emisorOtrasSenasExtranjero != '' && strlen($emisorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { + $xmlString .= ' + ' . $emisorOtrasSenasExtranjero . ''; + } + + if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { + $xmlString .= ' + + ' . $emisorCodPaisTel . ' + ' . $emisorTel . ' + '; + } + + if ($emisorEmail != '' && preg_match(EMAIL_REGEX, trim($emisorEmail))) { + $xmlString .= '' . trim($emisorEmail) . ''; + } else { + tools_reply([ + "Status" => 400, + "text" => "El email del emisor ($emisorEmail) no tiene un formato válido.", + ], true); + } + + + $xmlString .= ' + ' . $receptorNombre . ''; + + $xmlString .= ' + + ' . $receptorTipoIdentif . ' + ' . $receptorNumIdentif . ' + '; + + if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { + $xmlString .= ' + ' + . $receptorIdentifExtranjero . + ''; + } + + if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { + $xmlString .= ' + ' . $receptorNombreComercial . ''; + } + + if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { + $xmlString .= ' + + ' . $receptorProvincia . ' + ' . $receptorCanton . ' + ' . $receptorDistrito . ''; + if ($receptorBarrio != '') { + $xmlString .= '' . $receptorBarrio . ''; + } + $xmlString .= ' + ' . $receptorOtrasSenas . ' + '; + } + + if ($receptorCodPaisTel != '' && $receptorTel != '') { + $xmlString .= ' + + ' . $receptorCodPaisTel . ' + ' . $receptorTel . ' + '; + } + + if ($receptorEmail != '') { + $xmlString .= '' . $receptorEmail . ''; + } + + $xmlString .= ''; + + $xmlString .= ' + ' . $condVenta . ''; + + if (isset($condVentaOtros) && $condVentaOtros != "") { + $xmlString .= ' + ' . $condVentaOtros . ''; + } + + if (isset($plazoCredito) && $plazoCredito != "") { + $xmlString .= ' + ' . $plazoCredito . ''; + } + + $xmlString .= ' + '; + + // cant - unidad medida - detalle - precio unitario - monto total - subtotal - monto total linea - Monto desc -Naturaleza Desc - Impuesto : Codigo / Tarifa / Monto + /* EJEMPLO DE DETALLES + { + "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], + "2":["1","Sp","Honorarios","100000","100000","100000","100000"] + } + */ + $l = 1; + foreach ($detalles as $d) { + $xmlString .= ' + + ' . $l . ''; + + $xmlString .= ' + ' . $d->codigoCABYS . ''; + + if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { + // Convertir el objeto $d->codigoComercial en un array + $codigoComercialArray = (array) $d->codigoComercial; + + // Delimitar el array a solo 5 elementos + if (count($codigoComercialArray) > 5) { + error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); + } + $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); + + // Iterar sobre los elementos del array + foreach ($codigoComercialArray as $codigos) { + $c = (array) $codigos; + // Verificar si el elemento es un array asociativo + if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { + $xmlString .= ' + + ' . $c['tipo'] . ' + ' . $c['codigo'] . ' + '; + } + } + } + + $xmlString .= ' + ' . $d->cantidad . ' + ' . $d->unidadMedida . ''; + if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { + $xmlString .= ' + ' . $d->tipoTransaccion . ''; + } + if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { + $xmlString .= ' + ' . $d->unidadMedidaComercial . ''; + } + $d->detalle = limpiarTexto($d->detalle, 150); + $xmlString .= ' + ' . $d->detalle . ''; + if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { + $xmlString .= '' . $d->numeroVINoSerie . ''; + } + + if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { + $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; + } + if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { + $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; + } + + $xmlString .= ' + ' . $d->precioUnitario . ' + ' . $d->montoTotal . ''; + + if (isset($d->descuento) && !empty($d->descuento)) { + $descuentoArray = (array) $d->descuento; + + if (count($descuentoArray) > 5) { + error_log("descuento: " . count($descuentoArray) . " is greater than 5"); + } + $descuentoArray = array_slice($descuentoArray, 0, 5); + + foreach ($descuentoArray as $descuentos) { + $c = (array) $descuentos; + if ( + is_array($c) && + isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && + isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" + ) { + $xmlString .= ' + + ' . $c['montoDescuento'] . ' + ' . $c['codigoDescuento'] . ''; + // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo + if ( + isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && + isset($c['codigoDescuentoOTRO']) && + strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 + ) { + $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; + } + // NaturalezaDescuento: minOccurs=0, longitud 3-80 + if ( + isset($c['naturalezaDescuento']) && + strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 + ) { + $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; + } + $xmlString .= ' + '; + } + } + } + + $xmlString .= '' . $d->subTotal . ''; + + if (isset($d->baseImponible) && $d->baseImponible != "") { + $xmlString .= '' . $d->baseImponible . ''; + } + + if (isset($d->impuesto) && $d->impuesto != "") { + foreach ($d->impuesto as $i) { + $xmlString .= ' + + ' . $i->codigo . ''; + + // Add if required + if ( + isset($i->codigo) && $i->codigo == "99" && + isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) + ) { + $xmlString .= '' . $i->codigoImpuestoOtro . ''; + } + + if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { + $xmlString .= '' . $i->codigoTarifa . ''; + } + + if (isset($i->tarifa) && $i->tarifa != "") { + $xmlString .= '' . $i->tarifa . ''; + } + + if (isset($i->factorIVA) && $i->factorIVA != "") { + $xmlString .= '' . $i->factorIVA . ''; + } + + $xmlString .= '' . $i->monto . ''; + + if (isset($i->exoneracion) && $i->exoneracion != "") { + $xmlString .= ' + + ' . $i->exoneracion->tipoDocumento . ''; + if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { + $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; + } + $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; + if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { + $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; + } + if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { + $xmlString .= '' . $i->exoneracion->numeroInciso . ''; + } + $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; + if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { + $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; + } + $xmlString .= ' + ' . $i->exoneracion->fechaEmision . ' + ' . $i->exoneracion->tarifaExoneracion . ' + ' . $i->exoneracion->montoExoneracion . ' + '; + } + + $xmlString .= ''; + } + } + + $xmlString .= '' . $d->impuestoNeto . ''; + $xmlString .= '' . $d->montoTotalLinea . ''; + $xmlString .= ''; + $l++; + } + + $xmlString .= ''; + //OtrosCargos + if (isset($otrosCargos) && $otrosCargos != "") { + foreach ($otrosCargos as $o) { + $xmlString .= ' + + ' . $o->tipoDocumentoOC . ''; + if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { + $xmlString .= ' + ' . $o->tipoDocumentoOTROS . ''; + } + if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { + $xmlString .= ' + + ' . $o->tipoIdentidadTercero . ' + ' . $o->numeroIdentidadTercero . ' + '; + } + if (isset($o->nombreTercero) && $o->nombreTercero != "") { + $xmlString .= ' + ' . $o->nombreTercero . ''; + } + $o->detalle = limpiarTexto($o->detalle, 150); + $xmlString .= ' + ' . $o->detalle . ''; + if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { + $xmlString .= ' + ' . $o->porcentajeOC . ''; + } + $xmlString .= ' + ' . $o->montoCargo . ''; + $xmlString .= ' + '; + } + } + + $xmlString .= ' + '; + + if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { + $xmlString .= ' + + ' . $codMoneda . ' + ' . $tipoCambio . ' + '; + } else { + $xmlString .= ' + + CRC + 1 + '; + } + + if ($totalServGravados != '') { + $xmlString .= ' + ' . $totalServGravados . ''; + } + + if ($totalServExentos != '') { + $xmlString .= ' + ' . $totalServExentos . ''; + } + + if ($totalServExonerado != '') { + $xmlString .= ' + ' . $totalServExonerado . ''; + } + + if ($totalServNoSujeto != '') { + $xmlString .= ' + ' . $totalServNoSujeto . ''; + } + + if ($totalMercanciasGravadas != '') { + $xmlString .= ' + ' . $totalMercanciasGravadas . ''; + } + + if ($totalMercanciasExentas != '') { + $xmlString .= ' + ' . $totalMercanciasExentas . ''; + } + + if ($totalMercExonerada != '') { + $xmlString .= ' + ' . $totalMercExonerada . ''; + } + + if ($totalMercNoSujeta != '') { + $xmlString .= ' + ' . $totalMercNoSujeta . ''; + } + + if ($totalGravado != '') { + $xmlString .= ' + ' . $totalGravado . ''; + } + + if ($totalExento != '') { + $xmlString .= ' + ' . $totalExento . ''; + } + + if ($totalExonerado != '') { + $xmlString .= ' + ' . $totalExonerado . ''; + } + + if ($totalNoSujeto != '') { + $xmlString .= ' + ' . $totalNoSujeto . ''; + } + + $xmlString .= ' + ' . $totalVenta . ''; + + if ($totalDescuentos != '') { + $xmlString .= ' + ' . $totalDescuentos . ''; + } + + $xmlString .= ' + ' . $totalVentasNeta . ''; + + // Add logic for TotalDesgloseImpuesto + if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { + foreach ($totalDesgloseImpuesto as $impuesto) { + $xmlString .= ' + '; + if (isset($impuesto->Codigo)) { + $xmlString .= '' . $impuesto->Codigo . ''; + } + if (isset($impuesto->CodigoTarifaIVA)) { + $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; + } + if (isset($impuesto->TotalMontoImpuesto)) { + $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; + } + $xmlString .= ''; + } + } + + if ($totalImp != '') { + $xmlString .= ' + ' . $totalImp . ''; + } + + if ($totalImpAsumidoEmisorFabrica != '') { + $xmlString .= ' + ' . $totalImpAsumidoEmisorFabrica . ''; + } + + if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { + $xmlString .= ' + ' . $totalOtrosCargos . ''; + } + + if (isset($mediosPago) && !empty($mediosPago)) { + foreach ($mediosPago as $o) { + $xmlString .= ' + '; + + // Add TipoMedioPago + if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { + $xmlString .= '' . $o->tipoMedioPago . ''; + } + + // Add MedioPagoOtros (only if TipoMedioPago is "99") + if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { + $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; + } + + // Add TotalMedioPago + if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { + $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; + } + + $xmlString .= ''; + } + } + + $xmlString .= ' + ' . $totalComprobante . ' + '; + + if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { + foreach ($informacionReferencia as $ref) { + if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { + if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { + $xmlString .= ''; + $xmlString .= '' . $ref->tipoDoc . ''; + if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { + $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; + } + if (isset($ref->numero)) { + $xmlString .= '' . $ref->numero . ''; + } + $xmlString .= '' . $ref->fechaEmision . ''; + if (isset($ref->codigo)) { + $xmlString .= '' . $ref->codigo . ''; + if ($ref->codigo === '99' && isset($ref->codigoOtro)) { + $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; + } + } + if (isset($ref->razon)) { + $xmlString .= '' . $ref->razon . ''; + } + $xmlString .= ''; + } else { + grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); + } + } + } + } + + // JSON de ejemplo + // { + // "otroTexto": { + // "codigo": "COD1", + // "texto": "Texto opcional 1" + // }, + // "otroContenido": [ + // { + // "codigo": "CONT1", + // "contenidoEstructurado": { + // "ContactoDesarrollador": { + // "Correo": "developer@example.com", + // "Nombre": "Developer Name", + // "Telefono": "+123456789" + // } + // } + // }, + // { + // "codigo": "CONT2", + // "contenidoEstructurado": { + // "SoporteTecnico": { + // "Correo": "support@example.com", + // "Nombre": "Support Team", + // "Telefono": "+987654321" + // } + // } + // } + // ] + //} + + // Start Otros element + $xmlString .= ''; + + // Handle multiple OtroTexto elements + if (isset($otros->otroTexto)) { + if (is_array($otros->otroTexto)) { + foreach ($otros->otroTexto as $otroTexto) { + $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; + $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } else { + $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; + $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } + + // Handle multiple OtroContenido elements + if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { + foreach ($otros->otroContenido as $otroContenido) { + $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; + $contenido = ''; + if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { + foreach ($otroContenido->contenidoEstructurado as $tag => $data) { + $contenido .= '<' . $tag . '>'; + if (is_object($data)) { + foreach ($data as $k => $v) { + $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; + } + } + $contenido .= ''; + } + } + $xmlString .= '' . $contenido . ''; + } + } + + $xmlString .= ''; + + // XML Resultante + // + // Texto opcional 1 + // + // + // developer@example.com + // Developer Name + // +123456789 + // + // + // + // + // support@example.com + // Support Team + // +987654321 + // + // + // + + $xmlString .= ' + '; + + return array( + "clave" => $clave, + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) + ); +} + +function genXMLFee() +{ + $clave = params_get("clave"); + $proveedorSistemas = params_get("proveedor_sistemas"); + $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json + $consecutivo = params_get("consecutivo"); + $fechaEmision = params_get("fecha_emision"); + + $emisorNombre = params_get("emisor_nombre"); + $emisorTipoIdentif = params_get("emisor_tipo_identif"); + $emisorNumIdentif = params_get("emisor_num_identif"); + $emisorNombreComercial = params_get("emisor_nombre_comercial"); + $emisorProv = params_get("emisor_provincia"); + $emisorCanton = params_get("emisor_canton"); + $emisorDistrito = params_get("emisor_distrito"); + $emisorBarrio = params_get("emisor_barrio"); + $emisorOtrasSenas = params_get("emisor_otras_senas"); + $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); + $emisorTel = params_get("emisor_tel"); + $emisorEmail = params_get("emisor_email"); + $registroFiscal8707 = params_get("registrofiscal8707"); + + $receptorNombre = params_get("receptor_nombre"); + $receptorTipoIdentif = params_get("receptor_tipo_identif"); + $receptorNumIdentif = params_get("receptor_num_identif"); + $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); + $receptorNombreComercial = params_get("receptor_nombre_comercial"); + $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); + $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); + $receptorTel = params_get("receptor_tel"); + $receptorEmail = params_get("receptor_email"); + + $condVenta = params_get("condicion_venta"); + $condVentaOtros = params_get("condicion_venta_otros"); + $plazoCredito = params_get("plazo_credito"); + $detalles = json_decode(params_get("detalles")); + $otrosCargos = json_decode(params_get("otrosCargos")); + $codMoneda = params_get("cod_moneda"); + $tipoCambio = params_get("tipo_cambio"); + + $totalServGravados = params_get("total_serv_gravados"); + $totalServExentos = params_get("total_serv_exentos"); + $totalMercanciasGravadas = params_get("total_merc_gravada"); + $totalMercanciasExentas = params_get("total_merc_exenta"); + $totalGravado = params_get("total_gravados"); + $totalExento = params_get("total_exento"); + $totalVenta = params_get("total_ventas"); + $totalDescuentos = params_get("total_descuentos"); + $totalVentasNeta = params_get("total_ventas_neta"); + $totalImp = params_get("total_impuestos"); + $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); + $totalOtrosCargos = params_get("totalOtrosCargos"); + $totalComprobante = params_get("total_comprobante"); + + $informacionReferencia = json_decode(params_get("informacion_referencia")); + $otros = json_decode(params_get('otros')); + // Resumen + $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); + + grace_debug(params_get("detalles")); + + if (isset($otrosCargos) && $otrosCargos != "") { + grace_debug(params_get("otrosCargos")); + } + + if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { + grace_debug(params_get("totalDesgloseImpuesto")); + } + + // Validate string sizes + $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); + if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { + error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); + } + + if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { + error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); + } + + if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { + error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); + } + + if (strlen($receptorOtrasSenasExtranjero) > RECEPTOROTRASSENASMAXSIZE) { + error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenasExtranjero); + } + + if (isset($otrosCargos) && !empty($otrosCargos)) { + if (count($otrosCargos->otrosCargos) > 15) { + error_log("otrosCargos: " . count($otrosCargos->otrosCargos) . " is greater than 15"); + //Delimita el array a solo 4 elementos + $otrosCargos->otrosCargos = array_slice($otrosCargos->otrosCargos, 0, 15); + } + } + + $xmlString = ' + + ' . $clave . ' + ' . $proveedorSistemas . ' + ' . $codigoActividadEmisor . ' + ' . $consecutivo . ' + ' . $fechaEmision . ' + + ' . $emisorNombre . ' + + ' . $emisorTipoIdentif . ' + ' . $emisorNumIdentif . ' + '; + + if (isset($registroFiscal8707) && $registroFiscal8707 != "") { + $xmlString .= ' + ' . $registroFiscal8707 . ''; + } + + if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { + $xmlString .= ' + ' . $emisorNombreComercial . ''; + } + + if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { + $xmlString .= ' + + ' . $emisorProv . ' + ' . $emisorCanton . ' + ' . $emisorDistrito . ''; + if ($emisorBarrio != '') { + $xmlString .= '' . $emisorBarrio . ''; + } + $xmlString .= ' + ' . $emisorOtrasSenas . ' + '; + } + + if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { + $xmlString .= ' + + ' . $emisorCodPaisTel . ' + ' . $emisorTel . ' + '; + } + + if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { + $xmlString .= '' . trim($emisorEmail) . ''; + } else { + error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); + } + + if (isset($receptorNombre) && $receptorNombre != "") { + $xmlString .= ' + ' . $receptorNombre . ''; + } + + if (isset($receptorTipoIdentif) && $receptorTipoIdentif != "" && isset($receptorNumIdentif) && $receptorNumIdentif != "") { + $xmlString .= ' + + ' . $receptorTipoIdentif . ' + ' . $receptorNumIdentif . ' + '; + } + + if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { + $xmlString .= ' + ' + . $receptorIdentifExtranjero . + ''; + } + + if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { + $xmlString .= ' + ' . $receptorNombreComercial . ''; + } + + if (isset($receptorProvincia) && $receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { + $xmlString .= ' + + ' . $receptorProvincia . ' + ' . $receptorCanton . ' + ' . $receptorDistrito . ''; + if ($receptorBarrio != '') { + $xmlString .= '' . $receptorBarrio . ''; + } + $xmlString .= ' + ' . $receptorOtrasSenas . ' + '; + } + + if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { + $xmlString .= ' + ' + . $receptorOtrasSenasExtranjero . + ''; + } + + + if ($receptorCodPaisTel != '' && $receptorTel != '') { + $xmlString .= ' + + ' . $receptorCodPaisTel . ' + ' . $receptorTel . ' + '; + } + + if ($receptorEmail != '') { + $xmlString .= '' . $receptorEmail . ''; + $xmlString .= ''; + } + + $xmlString .= ' + ' . $condVenta . ''; + + if (isset($condVentaOtros) && $condVentaOtros != "") { + $xmlString .= ' + ' . $condVentaOtros . ''; + } + + if (isset($plazoCredito) && $plazoCredito != "") { + $xmlString .= ' + ' . $plazoCredito . ''; + } + + $xmlString .= ' + '; + + + $l = 1; + foreach ($detalles as $d) { + $xmlString .= ' + + ' . $l . ''; + + if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { + $xmlString .= ' + ' . $d->partidaArancelaria . ''; + } + + $xmlString .= ' + ' . $d->codigoCABYS . ''; + + if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { + // Convertir el objeto $d->codigoComercial en un array + $codigoComercialArray = (array) $d->codigoComercial; + + // Delimitar el array a solo 5 elementos + if (count($codigoComercialArray) > 5) { + error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); + } + $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); + + // Iterar sobre los elementos del array + foreach ($codigoComercialArray as $codigos) { + $c = (array) $codigos; + // Verificar si el elemento es un array asociativo + if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { + $xmlString .= ' + + ' . $c['tipo'] . ' + ' . $c['codigo'] . ' + '; + } + } + } + + + $xmlString .= ' + ' . $d->cantidad . ' + ' . $d->unidadMedida . ''; + if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { + $xmlString .= ' + ' . $d->tipoTransaccion . ''; + } + if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { + $xmlString .= ' + ' . $d->unidadMedidaComercial . ''; + } + $d->detalle = limpiarTexto($d->detalle, 150); + $xmlString .= ' + ' . $d->detalle . ''; + if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { + $xmlString .= '' . $d->numeroVINoSerie . ''; + } + + if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { + $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; + } + if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { + $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; + } + + if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { + $xmlString .= ''; + $lineas = array_slice($d->detalleSurtido, 0, 20); + foreach ($lineas as $linea) { + $xmlString .= ''; + $xmlString .= '' . $linea->codigoCABYSSurtido . ''; + if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { + $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); + foreach ($codigos as $codigo) { + $xmlString .= ''; + $xmlString .= '' . $codigo->tipoSurtido . ''; + $xmlString .= '' . $codigo->codigoSurtido . ''; + $xmlString .= ''; + } + } + + $xmlString .= '' . $linea->cantidadSurtido . ''; + $xmlString .= '' . $linea->unidadMedidaSurtido . ''; + if (isset($linea->unidadMedidaComercialSurtido)) { + $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; + } + + $xmlString .= '' . $linea->detalleSurtido . ''; + $xmlString .= '' . $linea->precioUnitarioSurtido . ''; + $xmlString .= '' . $linea->montoTotalSurtido . ''; + if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { + $descuentos = array_slice($linea->descuentoSurtido, 0, 5); + foreach ($descuentos as $desc) { + $xmlString .= ''; + $xmlString .= '' . $desc->montoDescuentoSurtido . ''; + $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; + if (isset($desc->descuentoSurtidoOtros)) { + $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; + } + $xmlString .= ''; + } + } + $xmlString .= '' . $linea->subTotalSurtido . ''; + if (isset($linea->ivaCobradoFabricaSurtido)) { + $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; + } + $xmlString .= '' . $linea->baseImponibleSurtido . ''; + if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { + $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); + foreach ($impuestos as $imp) { + $xmlString .= ''; + $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; + if (isset($imp->codigoImpuestoOTROSurtido)) { + $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; + } + if (isset($imp->codigoTarifaIVASurtido)) { + $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; + } + if (isset($imp->tarifaSurtido)) { + $xmlString .= '' . $imp->tarifaSurtido . ''; + } + if (isset($imp->datosImpuestoEspecificoSurtido)) { + $e = $imp->datosImpuestoEspecificoSurtido; + $xmlString .= ''; + if (isset($e->cantidadUnidadMedidaSurtido)) { + $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; + } + if (isset($e->porcentajeSurtido)) { + $xmlString .= '' . $e->porcentajeSurtido . ''; + } + if (isset($e->proporcionSurtido)) { + $xmlString .= '' . $e->proporcionSurtido . ''; + } + if (isset($e->volumenUnidadConsumoSurtido)) { + $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; + } + if (isset($e->impuestoUnidadSurtido)) { + $xmlString .= '' . $e->impuestoUnidadSurtido . ''; + } + $xmlString .= ''; + } + $xmlString .= '' . $imp->montoImpuestoSurtido . ''; + $xmlString .= ''; + } + } + $xmlString .= ''; + } + $xmlString .= ''; + } + + $xmlString .= ' + ' . $d->precioUnitario . ' + ' . $d->montoTotal . ''; + + if (isset($d->descuento) && !empty($d->descuento)) { + $descuentoArray = (array) $d->descuento; + + if (count($descuentoArray) > 5) { + error_log("descuento: " . count($descuentoArray) . " is greater than 5"); + } + $descuentoArray = array_slice($descuentoArray, 0, 5); + + foreach ($descuentoArray as $descuentos) { + $c = (array) $descuentos; + if ( + is_array($c) && + isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && + isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" + ) { + $xmlString .= ' + + ' . $c['montoDescuento'] . ' + ' . $c['codigoDescuento'] . ''; + // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo + if ( + isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && + isset($c['codigoDescuentoOTRO']) && + strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 + ) { + $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; + } + // NaturalezaDescuento: minOccurs=0, longitud 3-80 + if ( + isset($c['naturalezaDescuento']) && + strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 + ) { + $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; + } + $xmlString .= ' + '; + } + } + } + + $xmlString .= '' . $d->subTotal . ''; + + if (isset($d->impuesto) && $d->impuesto != "") { + foreach ($d->impuesto as $i) { + $xmlString .= ' + '; + if (isset($i->codigo) && $i->codigo != "") { + $xmlString .= '' . $i->codigo . ''; + } + + // Add if required + if ( + isset($i->codigo) && $i->codigo == "99" && + isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) + ) { + $xmlString .= '' . $i->codigoImpuestoOtro . ''; + } + + if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { + $xmlString .= '' . $i->codigoTarifa . ''; + } + + if (isset($i->tarifa) && $i->tarifa != "") { + $xmlString .= '' . $i->tarifa . ''; + } + + if (isset($i->factorIVA) && $i->factorIVA != "") { + $xmlString .= '' . $i->factorIVA . ''; + } + + if (isset($i->monto) && $i->monto != "") { + $xmlString .= '' . $i->monto . ''; + } + + if (isset($i->montoExportacion) && $i->montoExportacion != "") { + $xmlString .= '' . $i->montoExportacion . ''; + } + + $xmlString .= ''; + } + } + + if (isset($d->impuestoNeto) && $d->impuestoNeto != "") { + $xmlString .= '' . $d->impuestoNeto . ''; + } + $xmlString .= '' . $d->montoTotalLinea . ''; + $xmlString .= ''; + $l++; + } + + $xmlString .= ''; + + // JSON DE EJEMPLO + // [ + // { + // "tipoDocumentoOC": "10", + // "tipoDocumentoOTROS": "string", + // "tipoIdentidadTercero": "01", + // "numeroIdentidadTercero": "160029688", + // "nombreTercero": "John Doe", + // "detalle": "Additional charge for service", + // "porcentajeOC": "1452590.23", + // "montoCargo": "1258720.23491" + // }, + // { + // "tipoDocumentoOC": "20", + // "tipoDocumentoOTROS": "other", + // "tipoIdentidadTercero": "02", + // "numeroIdentidadTercero": "123456789", + // "nombreTercero": "Jane Smith", + // "detalle": "Extra fee for expedited processing", + // "porcentajeOC": "10.50", + // "montoCargo": "500.00" + // } + // ] + + //OtrosCargos + if (isset($otrosCargos) && $otrosCargos != "") { + foreach ($otrosCargos as $o) { + $xmlString .= ' + + ' . $o->tipoDocumentoOC . ''; + if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { + $xmlString .= ' + ' . $o->tipoDocumentoOTROS . ''; + } + if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { + $xmlString .= ' + + ' . $o->tipoIdentidadTercero . ' + ' . $o->numeroIdentidadTercero . ' + '; + } + if (isset($o->nombreTercero) && $o->nombreTercero != "") { + $xmlString .= ' + ' . $o->nombreTercero . ''; + } + $o->detalle = limpiarTexto($o->detalle, 150); + $xmlString .= ' + ' . $o->detalle . ''; + if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { + $xmlString .= ' + ' . $o->porcentajeOC . ''; + } + $xmlString .= ' + ' . $o->montoCargo . ''; + $xmlString .= ' + '; + } + } + + // XML Resultante + // + // 10 + // string + // + // 01 + // 160029688 + // + // John Doe + // Additional charge for service + // 1452590.23 + // 1258720.23491 + // + // + // 20 + // other + // + // 02 + // 123456789 + // + // Jane Smith + // Extra fee for expedited processing + // 10.50 + // 500.00 + // + + $xmlString .= ' + '; + + if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { + $xmlString .= ' + + ' . $codMoneda . ' + ' . $tipoCambio . ' + '; + } else { + $xmlString .= ' + + CRC + 1 + '; + } + + if ($totalServGravados != '') { + $xmlString .= ' + ' . $totalServGravados . ''; + } + + if ($totalServExentos != '') { + $xmlString .= ' + ' . $totalServExentos . ''; + } + + if ($totalMercanciasGravadas != '') { + $xmlString .= ' + ' . $totalMercanciasGravadas . ''; + } + + if ($totalMercanciasExentas != '') { + $xmlString .= ' + ' . $totalMercanciasExentas . ''; + } + + if ($totalGravado != '') { + $xmlString .= ' + ' . $totalGravado . ''; + } + + if ($totalExento != '') { + $xmlString .= ' + ' . $totalExento . ''; + } + + $xmlString .= ' + ' . $totalVenta . ''; + + if ($totalDescuentos != '') { + $xmlString .= ' + ' . $totalDescuentos . ''; + } + + $xmlString .= ' + ' . $totalVentasNeta . ''; + + // Add logic for TotalDesgloseImpuesto + if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { + foreach ($totalDesgloseImpuesto as $impuesto) { + $xmlString .= ' + '; + if (isset($impuesto->Codigo)) { + $xmlString .= '' . $impuesto->Codigo . ''; + } + if (isset($impuesto->CodigoTarifaIVA)) { + $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; + } + if (isset($impuesto->TotalMontoImpuesto)) { + $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; + } + $xmlString .= ''; + } + } + + if ($totalImp != '') { + $xmlString .= ' + ' . $totalImp . ''; + } + + if ($totalImpAsumidoEmisorFabrica != '') { + $xmlString .= ' + ' . $totalImpAsumidoEmisorFabrica . ''; + } + + if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { + $xmlString .= ' + ' . $totalOtrosCargos . ''; + } + + if (isset($mediosPago) && !empty($mediosPago)) { + foreach ($mediosPago as $o) { + $xmlString .= ' + '; + + // Add TipoMedioPago + if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { + $xmlString .= '' . $o->tipoMedioPago . ''; + } + + // Add MedioPagoOtros (only if TipoMedioPago is "99") + if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { + $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; + } + + // Add TotalMedioPago + if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { + $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; + } + + $xmlString .= ''; + } + } + + + $xmlString .= ' + ' . $totalComprobante . ' + '; + + // JSON de ejemplo + // { + // "informacionReferencia": [ + // { + // "tipoDoc": "01", + // "tipoDocOtro": "Factura", + // "numero": "50620032400020536006000100001010000000017100000017", + // "fechaEmision": "2023-10-01T12:00:00", + // "codigo": "99", + // "codigoOtro": "OTRO1", + // "razon": "Corrección de datos" + // }, + // { + // "tipoDoc": "02", + // "numero": "50620032400020536006000100001010000000017200000018", + // "fechaEmision": "2023-10-02T15:30:00", + // "codigo": "01", + // "razon": "Devolución de producto" + // } + // ] + // } + + if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { + foreach ($informacionReferencia as $ref) { + if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { + if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { + $xmlString .= ''; + $xmlString .= '' . $ref->tipoDoc . ''; + if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { + $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; + } + if (isset($ref->numero)) { + $xmlString .= '' . $ref->numero . ''; + } + $xmlString .= '' . $ref->fechaEmision . ''; + if (isset($ref->codigo)) { + $xmlString .= '' . $ref->codigo . ''; + if ($ref->codigo === '99' && isset($ref->codigoOtro)) { + $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; + } + } + if (isset($ref->razon)) { + $xmlString .= '' . $ref->razon . ''; + } + $xmlString .= ''; + } else { + grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); + } + } + } + } + + // XML Resultante + // + // 01 + // Factura + // 50620032400020536006000100001010000000017100000017 + // 2023-10-01T12:00:00 + // 99 + // OTRO1 + // Corrección de datos + // + // + // 02 + // 50620032400020536006000100001010000000017200000018 + // 2023-10-02T15:30:00 + // 01 + // Devolución de producto + // + + // ----------------------------------------------------------------------------------------------------- + + // JSON de ejemplo + // { + // "otroTexto": { + // "codigo": "COD1", + // "texto": "Texto opcional 1" + // }, + // "otroContenido": [ + // { + // "codigo": "CONT1", + // "contenidoEstructurado": { + // "ContactoDesarrollador": { + // "Correo": "developer@example.com", + // "Nombre": "Developer Name", + // "Telefono": "+123456789" + // } + // } + // }, + // { + // "codigo": "CONT2", + // "contenidoEstructurado": { + // "SoporteTecnico": { + // "Correo": "support@example.com", + // "Nombre": "Support Team", + // "Telefono": "+987654321" + // } + // } + // } + // ] + //} + + // Start Otros element + $xmlString .= ''; + + // Handle multiple OtroTexto elements + if (isset($otros->otroTexto)) { + if (is_array($otros->otroTexto)) { + foreach ($otros->otroTexto as $otroTexto) { + $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; + $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } else { + $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; + $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; + $xmlString .= '' . $texto . ''; + } + } + + // Handle multiple OtroContenido elements + if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { + foreach ($otros->otroContenido as $otroContenido) { + $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; + $contenido = ''; + if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { + foreach ($otroContenido->contenidoEstructurado as $tag => $data) { + $contenido .= '<' . $tag . '>'; + if (is_object($data)) { + foreach ($data as $k => $v) { + $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; + } + } + $contenido .= ''; + } + } + $xmlString .= '' . $contenido . ''; + } + } + + $xmlString .= ''; + + // XML Resultante + // + // Texto opcional 1 + // + // + // developer@example.com + // Developer Name + // +123456789 + // + // + // + // + // support@example.com + // Support Team + // +987654321 + // + // + // + + $xmlString .= ' + '; + + return array( + "clave" => $clave, + "xml" => base64_encode($xmlString), + "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) + ); +} \ No newline at end of file diff --git a/api/contrib/genXML/module.php b/api/contrib/genXML/module.php index 9d63c05d..57fd1b52 100644 --- a/api/contrib/genXML/module.php +++ b/api/contrib/genXML/module.php @@ -520,3 +520,4 @@ function MODULENAME_access() /**@}*/ /** @}*/ + diff --git a/www/xsd/NotaCreditoElectronica_V4.4.xsd b/www/xsd/NotaCreditoElectronica_V4.4.xsd index a0d240c7..7bd8c0f5 100644 --- a/www/xsd/NotaCreditoElectronica_V4.4.xsd +++ b/www/xsd/NotaCreditoElectronica_V4.4.xsd @@ -978,7 +978,6 @@ se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. - From 5e0710727f2c0ab06b75914bb422eaec19726681 Mon Sep 17 00:00:00 2001 From: David Obando Date: Mon, 22 Sep 2025 20:35:02 -0400 Subject: [PATCH 21/30] parametro no agregado al redondear valor --- api/contrib/genXML/genXML.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index ea7bd663..4903e12a 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -92,7 +92,7 @@ function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) if (!isset($calculados['totalDescuentos'])) { $calculados['totalDescuentos'] = 0; } - $calculados['totalDescuentos'] += round($totalDescuentos, DECIMALES); + $calculados['totalDescuentos'] += round(floatval($totalDescuentos), DECIMALES); if (!isset($calculados[$numLinea])) { $calculados[$numLinea] = []; From ea7dbe8cd84806357aa08117138f7bcf65b02bd0 Mon Sep 17 00:00:00 2001 From: David Obando Date: Mon, 22 Sep 2025 21:42:59 -0400 Subject: [PATCH 22/30] fix: definir DECIMALES solo si no se ha definido --- api/contrib/genXML/genXML.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 4903e12a..68fb4327 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -19,7 +19,10 @@ /* * ************************************************** */ /* Constantes de validacion */ /* * ************************************************** */ -define("DECIMALES", 5); + +if (!defined('DECIMALES')) { + define("DECIMALES", 5); +} define("TIPODOCREFVALUES", array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '99')); define('CODIDOREFVALUES', array('01', '02', '04', '05', '06', '07', '08', '09', '10', '11', '12', '99')); From 25aa82b22d4dc44a3b6ca85e930f360ecfda8033 Mon Sep 17 00:00:00 2001 From: David Obando Date: Mon, 22 Sep 2025 21:44:20 -0400 Subject: [PATCH 23/30] fix uso de comillas inconsistente --- api/contrib/genXML/genXML.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 68fb4327..38f23beb 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -21,7 +21,7 @@ /* * ************************************************** */ if (!defined('DECIMALES')) { - define("DECIMALES", 5); + define('DECIMALES', 5); } define("TIPODOCREFVALUES", array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '99')); define('CODIDOREFVALUES', array('01', '02', '04', '05', '06', '07', '08', '09', '10', '11', '12', '99')); From 351bd70e61cf8647b9aacc042eec2afea443f6eb Mon Sep 17 00:00:00 2001 From: David Obando Date: Tue, 23 Sep 2025 13:37:24 -0400 Subject: [PATCH 24/30] medio de pago auto cuando se pasa -1 --- api/contrib/genXML/genXML.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 38f23beb..f469890f 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -1127,6 +1127,12 @@ function genXMLGenerico($tipoDocumento = 'FE') if (isset($mediosPago) && !empty($mediosPago)) { $totalMediosPago = 0; foreach ($mediosPago as $o) { + + // usar el total de comprobante como pago cuando se pasa un -1 + if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago) && $o->totalMedioPago == -1) { + $o->totalMedioPago = $calculados['totalComprobante']; + } + $xmlString .= ' '; @@ -1141,7 +1147,7 @@ function genXMLGenerico($tipoDocumento = 'FE') } // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { + if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago) && $o->totalMedioPago >= 0) { $xmlString .= '' . round($o->totalMedioPago, DECIMALES) . ''; } From d259b0b926dd2c7bd4c0f3ec5b3dda4a2889a07a Mon Sep 17 00:00:00 2001 From: David Obando Date: Tue, 23 Sep 2025 23:16:52 -0400 Subject: [PATCH 25/30] remover condicion de requerido del codigo cabys para notas de credito --- api/contrib/genXML/genXML.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index f469890f..6955705b 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -613,18 +613,14 @@ function genXMLGenerico($tipoDocumento = 'FE') foreach ($detalles as $d) { - foreach (["codigoCABYS"] as $requiredField) { - if (!isset($d->{$requiredField}) || $d->{$requiredField} === '') { - tools_reply("Se requiere el campo $requiredField en el detalle #$l", true); - } - } - $xmlString .= ' ' . $l . ''; - $xmlString .= ' + if (isset($d->codigoCABYS) && $d->codigoCABYS != "") { + $xmlString .= ' ' . $d->codigoCABYS . ''; + } if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { // Convertir el objeto $d->codigoComercial en un array From f10f714e837c302b781b4934c79258c2e1dda9a5 Mon Sep 17 00:00:00 2001 From: David Obando Date: Sun, 5 Oct 2025 22:46:10 -0400 Subject: [PATCH 26/30] fix: ultimos cambios --- api/contrib/consultar/consultar.php | 150 +++++++++++++++++----------- api/core/tools.php | 1 + 2 files changed, 90 insertions(+), 61 deletions(-) diff --git a/api/contrib/consultar/consultar.php b/api/contrib/consultar/consultar.php index 6905ff4a..39e6225d 100644 --- a/api/contrib/consultar/consultar.php +++ b/api/contrib/consultar/consultar.php @@ -18,8 +18,8 @@ function consultarRecepcion() { - $curl = curl_init(); - $clave = params_get('clave'); + $curl = curl_init(); + $clave = params_get('clave'); if ($clave == "" || strlen($clave) == 0) return "La clave no puede ser en blanco"; @@ -31,20 +31,20 @@ function consultarRecepcion() $url = "https://api.comprobanteselectronicos.go.cr/recepcion/v1/recepcion/"; if ($url == null) - return "El client_id proprocionado (".params_get("client_id").") no es válido. "; + return "El client_id proporcionado (" . params_get("client_id") . ") no es válido. "; $args = array( - CURLOPT_URL => $url . $clave, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HEADER => true, - CURLOPT_ENCODING => "", - CURLOPT_MAXREDIRS => 10, - CURLOPT_SSL_VERIFYHOST => 0, - CURLOPT_SSL_VERIFYPEER => 0, - CURLOPT_TIMEOUT => 30, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => "GET", - CURLOPT_HTTPHEADER => array( + CURLOPT_URL => $url . $clave, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_SSL_VERIFYHOST => 0, + CURLOPT_SSL_VERIFYPEER => 0, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "GET", + CURLOPT_HTTPHEADER => array( "Authorization: Bearer " . params_get('token'), "Cache-Control: no-cache", "Content-Type: application/x-www-form-urlencoded" @@ -52,46 +52,46 @@ function consultarRecepcion() ); curl_setopt_array($curl, $args); - $response = curl_exec($curl); + $response = curl_exec($curl); $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE); $headers = substr($response, 0, $header_size); $body = substr($response, $header_size); - $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); - $err = curl_error($curl); + $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $err = curl_error($curl); curl_close($curl); - + if ($err) { $arrayResp = array( "Status" => $status, "to" => $url, "text" => $err ); - + return $arrayResp; } - + $responseObj = json_decode($body); - if( is_object($responseObj) ) { + if (is_object($responseObj)) { // si hay respuesta de hacienda, procesar detalle de mensaje - if(isset($responseObj->{'respuesta-xml'})) { - + if (isset($responseObj->{'respuesta-xml'})) { + $xmlRespuesta = base64_decode($responseObj->{'respuesta-xml'}); $xmlRespuesta = preg_replace('/>\s+<', $xmlRespuesta); - $startLength = strlen("tiene los siguientes errores: ")+7; + $startLength = strlen("tiene los siguientes errores: ") + 7; $startPos = strpos($xmlRespuesta, "tiene los siguientes errores: ", 0); - if(!$startPos) { + if (!$startPos) { // si no hay errores, retornar la respuesta normal return $responseObj; } $endPos = strpos($xmlRespuesta, "", $startPos); - $detalleMensaje = substr($xmlRespuesta, $startPos+$startLength, $endPos -($startPos+$startLength+1)); + $detalleMensaje = substr($xmlRespuesta, $startPos + $startLength, $endPos - ($startPos + $startLength + 1)); $separadorMensaje = " \n"; - $responseObj->{'DetalleMensaje'} = explode($separadorMensaje,trim($detalleMensaje,$separadorMensaje)); + $responseObj->{'DetalleMensaje'} = explode($separadorMensaje, trim($detalleMensaje, $separadorMensaje)); } - + return array( "Status" => $status, "to" => $url, @@ -99,7 +99,7 @@ function consultarRecepcion() ); } - if(empty($body)) { + if (empty($body)) { $header_lines = explode("\r\n", trim($headers)); $header_array = []; foreach ($header_lines as $line) { @@ -122,13 +122,15 @@ function consultarRecepcion() ); } + function consultarComprobante() { - $curl = curl_init(); - $clave = params_get('clave'); + $curl = curl_init(); + $clave = params_get('clave'); - if ($clave == "" || strlen($clave) == 0) + if ($clave == "" || strlen($clave) == 0) { return "La clave no puede ser en blanco"; + } $url = null; if (params_get("client_id") == 'api-stag') @@ -136,46 +138,72 @@ function consultarComprobante() else if (params_get("client_id") == 'api-prod') $url = "https://api.comprobanteselectronicos.go.cr/recepcion/v1/comprobantes/"; - if ($url == null) - return "Ha ocurrido un error en el client_id."; + if ($url == null) { + return "El client_id proporcionado (" . params_get("client_id") . ") no es válido. "; + } $args = array( - CURLOPT_URL => $url . $clave, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => "", - CURLOPT_MAXREDIRS => 10, - CURLOPT_SSL_VERIFYHOST => 0, - CURLOPT_SSL_VERIFYPEER => 0, - CURLOPT_TIMEOUT => 30, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => "GET", - CURLOPT_HTTPHEADER => array( - "Authorization: Bearer " . params_get('token'), - "Cache-Control: no-cache", - "Content-Type: application/x-www-form-urlencoded" + CURLOPT_URL => $url . $clave, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_SSL_VERIFYHOST => 0, + CURLOPT_SSL_VERIFYPEER => 0, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "GET", + CURLOPT_HTTPHEADER => array( + "Authorization: Bearer " . params_get('token') ), ); - - curl_setopt_array($curl, $args); - $response = curl_exec($curl); - $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); - $err = curl_error($curl); + + curl_setopt_array($curl, $args); + $response = curl_exec($curl); + + $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE); + $headers = substr($response, 0, $header_size); + $body = substr($response, $header_size); + + $status = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $err = curl_error($curl); curl_close($curl); - if ($err) - { + if ($err) { $arrayResp = array( "Status" => $status, - "to" => $apiTo, + "to" => $url, "text" => $err ); - return $arrayResp; + + return tools_reply($arrayResp, true); + } - else - { - $response = json_decode($response); - return $response; + + if (empty($body)) { + $header_lines = explode("\r\n", trim($headers)); + $header_array = []; + foreach ($header_lines as $line) { + if (strpos($line, ':') !== false) { + list($key, $value) = explode(': ', $line, 2); + $header_array[$key] = $value; + } + } + return tools_reply(array( + "Status" => $header_array['x-http-status'] ?? $status, + "to" => $url, + "text" => $header_array['x-error-cause'] + ), true); } + + $responseObj = json_decode($body); + + return tools_reply(array( + "Status" => $status, + "to" => $url, + "text" => $body + ), true); + } -?> +?> \ No newline at end of file diff --git a/api/core/tools.php b/api/core/tools.php index 166be5d7..8942e462 100644 --- a/api/core/tools.php +++ b/api/core/tools.php @@ -93,6 +93,7 @@ function tools_reply($response, $killMe = false) ))); } + die("hola"); _tools_reply(tools_returnJson(array( 'status' => ($killMe ? 'error' : 'ok'), 'resp' => $response, From 81f6dbfdc61e759ccc8cb24cbcbcdeeac867855c Mon Sep 17 00:00:00 2001 From: David Obando Date: Thu, 4 Dec 2025 18:05:25 -0500 Subject: [PATCH 27/30] ultimos cambios --- api/core/tools.php | 1 - api/modules/wirez/messagesSend.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/core/tools.php b/api/core/tools.php index 8942e462..166be5d7 100644 --- a/api/core/tools.php +++ b/api/core/tools.php @@ -93,7 +93,6 @@ function tools_reply($response, $killMe = false) ))); } - die("hola"); _tools_reply(tools_returnJson(array( 'status' => ($killMe ? 'error' : 'ok'), 'resp' => $response, diff --git a/api/modules/wirez/messagesSend.php b/api/modules/wirez/messagesSend.php index 2b8330ba..bbde2186 100644 --- a/api/modules/wirez/messagesSend.php +++ b/api/modules/wirez/messagesSend.php @@ -75,7 +75,7 @@ function wirez_conversationsCreate($idUser, $subject, $idRecipient) db_escape($idUser), db_escape($idRecipient), $timestamp, - db_escape($subject); + db_escape($subject)); $r = db_query($insert, 0); From ceab7a50a5d7b26edcf13f9503ca899d4a68a2b6 Mon Sep 17 00:00:00 2001 From: David Obando Date: Wed, 31 Dec 2025 00:59:24 -0500 Subject: [PATCH 28/30] latest changes, fix on decimales --- .../xmlseclibs/src/XMLSecurityDSig.php | 7 + api/contrib/genXML/genXML.php | 455 +++++++++++++----- api/tools/ValidadorXML.php | 83 +++- 3 files changed, 421 insertions(+), 124 deletions(-) diff --git a/api/contrib/firmarXML/xmlseclibs/src/XMLSecurityDSig.php b/api/contrib/firmarXML/xmlseclibs/src/XMLSecurityDSig.php index 4d8d0a45..51d4d84e 100644 --- a/api/contrib/firmarXML/xmlseclibs/src/XMLSecurityDSig.php +++ b/api/contrib/firmarXML/xmlseclibs/src/XMLSecurityDSig.php @@ -283,6 +283,13 @@ public function setCanonicalMethod($method) public function setSignPolicy(){ $xmlns = $this->xmlFirstChild->getAttribute('xmlns'); switch ($xmlns){ + case (strpos($xmlns, 'v4.3') !== false): + $this->signPolicy = [ + "name" => "", + "url" => "https://cdn.comprobanteselectronicos.go.cr/xml-schemas/Resoluci%C3%B3n_General_sobre_disposiciones_t%C3%A9cnicas_comprobantes_electr%C3%B3nicos_para_efectos_tributarios.pdf", + "digest" => "DWxin1xWOeI8OuWQXazh4VjLWAaCLAA954em7DMh0h8=" // Base64_Encode(Hash_File(SHA_256)) + ]; + break; case (strpos($xmlns, 'v4.4') !== false): $this->signPolicy = [ "name" => "", diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 6955705b..eb96e8e0 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -39,29 +39,6 @@ tools_useTool('ValidadorXML.php'); -/** - * Devuelve true si todas las claves existen en el array - * - function array_all(array $keys, array $array) { - foreach ($keys as $key) { - if (!array_key_exists($key, $array)) { - return false; - } - } - return true; - } - - * Devuelve true si al menos una de las claves existe en el array - * -function array_any(array $keys, array $array) { - foreach ($keys as $key) { - if (array_key_exists($key, $array)) { - return true; - } - } - return false; -} */ - /** * Valida las reglas conocidas de una linea del detalle para evitar rechazos * @@ -81,6 +58,238 @@ function validarLinea($numLinea, $d) } } + +/** + * Obtiene el código de institución a partir del nombre + * + * @param string $nombreInstitucion Nombre de la institución + * @return string Código de la institución (01-12, 99) o '99' si no se encuentra + */ +function obtenerCodigoInstitucion($nombreInstitucion) +{ + $mapa = getMapaInstituciones(); + + // Normalizar: minúsculas, sin espacios, sin tildes + $nombreNormalizado = strtolower($nombreInstitucion); + $nombreNormalizado = str_replace(' ', '', $nombreNormalizado); + + // Eliminar tildes + $nombreNormalizado = str_replace( + ['á', 'é', 'í', 'ó', 'ú', 'ñ'], + ['a', 'e', 'i', 'o', 'u', 'n'], + $nombreNormalizado + ); + + // Buscar en el mapa + if (isset($mapa[$nombreNormalizado])) { + return $mapa[$nombreNormalizado]; + } + + // Si no se encuentra, retornar '99' (Otros) + error_log("Institución no encontrada en catálogo: {$nombreInstitucion}. Usando código '99' (Otros)"); + return '99'; +} + +/** + * Mapea los datos del API de exoneraciones de Hacienda al formato requerido por genXML + * + * @param array $datosAPI Datos recibidos del API de Hacienda + * @return array|false Datos mapeados al formato de ExoneracionType o false si hay error + */ +function mapearExoneracionHacienda($datosAPI) +{ + if (!$datosAPI || !is_array($datosAPI)) { + error_log('Datos de exoneración inválidos para mapear'); + return false; + } + + // Validar campos obligatorios del API + $camposRequeridos = ['numeroDocumento', 'tipoDocumento', 'nombreInstitucion', 'fechaEmision', 'porcentajeExoneracion']; + foreach ($camposRequeridos as $campo) { + if (!isset($datosAPI[$campo])) { + error_log("Campo obligatorio '{$campo}' no encontrado en respuesta del API de exoneraciones"); + return false; + } + } + + // Obtener código de institución a partir del nombre + $codigoInstitucion = obtenerCodigoInstitucion($datosAPI['nombreInstitucion']); + + $fechaEmision = $datosAPI['fechaEmision']; + + // Construir el objeto de exoneración mapeado + $exoneracion = [ + 'tipoDocumento' => $datosAPI['tipoDocumento']['codigo'], // Código del tipo de documento (ej: "04") + 'numeroDocumento' => $datosAPI['numeroDocumento'], // Número de autorización (ej: "AL-00028291-24") + 'nombreInstitucion' => $codigoInstitucion, // Código de institución (01-12, 99) + 'fechaEmision' => $fechaEmision, + 'tarifaExoneracion' => floatval($datosAPI['porcentajeExoneracion']), // Porcentaje (ej: 13.00) + ]; + + // Agregar número de autorización como artículo (opcional pero útil) + if (isset($datosAPI['autorizacion'])) { + $exoneracion['numeroArticulo'] = strval($datosAPI['autorizacion']); + } + + // Si el tipo de documento es "99" (Otro), agregar descripción + if ($datosAPI['tipoDocumento']['codigo'] === '99' && isset($datosAPI['tipoAutorizacion'])) { + $exoneracion['tipoDocumentoOtro'] = $datosAPI['tipoAutorizacion']; + } + + // Si la institución es "99" (Otros), agregar nombre completo + if ($codigoInstitucion === '99') { + $exoneracion['nombreInstitucionOtros'] = $datosAPI['nombreInstitucion']; + } + + return $exoneracion; +} + +/** + * Consulta el API de Hacienda para validar y obtener datos de una exoneración + * Retorna los datos mapeados listos para usar en el XML + * + * @param string $numeroDocumento Número del documento de exoneración (ej: "AL-00028291-24") + * @return array|false Array con datos de exoneración mapeados o false si hay error + */ +function consultarYMapearExoneracion($numeroDocumento) +{ + // Consultar el API de Hacienda + $datosAPI = consultarExoneracionHacienda($numeroDocumento); + + if ($datosAPI === false) { + return false; + } + + // Mapear los datos al formato requerido + $exoneracion = mapearExoneracionHacienda($datosAPI); + return $exoneracion; +} + +/** + * Mapa de instituciones (nombre normalizado => código) + * Los nombres están en minúsculas y sin espacios para facilitar la comparación + */ +function getMapaInstituciones() +{ + return [ + 'ministeriodehacienda' => '01', + 'ministeriodelaciones exterioresyculto' => '02', + 'ministeriodeagriculturayganadería' => '03', + 'ministeriodeagriculturayganaderia' => '03', // Sin tilde + 'ministerioeconomíaindustriaycomercio' => '04', + 'ministerioeconomiaindustriaycomercio' => '04', // Sin tilde + 'cruzrojacostarricense' => '05', + 'beneméritocoerpodebomberosdecostarica' => '06', + 'beneméritocuerpodebomberosdecostarica' => '06', + 'benemeritocoerpodebomberosdecostarica' => '06', // Sin tilde + 'asociaciónobrasdelespíritusanto' => '07', + 'asociacionobrasdelespiritusanto' => '07', // Sin tilde + 'federacióncruzadanacionaldeprotecciónalanciano(fecrunapa)' => '08', + 'federacioncruzadanacionaldeproteccionalanciano(fecrunapa)' => '08', // Sin tilde + 'federacioncruzadanacionaldeproteccionalancianofecrunapa' => '08', + 'fecrunapa' => '08', + 'escueladeagriculturadelaregiónhúmeda(earth)' => '09', + 'escueladeagriculturadelaregionhumeda(earth)' => '09', // Sin tilde + 'escueladeagriculturadelaregionhumeda' => '09', + 'earth' => '09', + 'institutocentroamericanodeadministracióndeempresas(incae)' => '10', + 'institutocentroamericanodeadministraciondeempresas(incae)' => '10', // Sin tilde + 'institutocentroamericanodeadministraciondeempresas' => '10', + 'incae' => '10', + 'juntadeprotecciónsocial(jps)' => '11', + 'juntadeproteccionsocial(jps)' => '11', // Sin tilde + 'juntadeproteccionsocial' => '11', + 'jps' => '11', + 'autoridadreguladoradelosserviciospúblicos(aresep)' => '12', + 'autoridadreguladoradelosserviciospublicos(aresep)' => '12', // Sin tilde + 'autoridadreguladoradelosserviciospublicos' => '12', + 'aresep' => '12', + 'otros' => '99' + ]; +} + +/** + * Consulta la API de Hacienda para validar una exoneración + * + * @param string $numeroDocumento Número del documento de exoneración + * @return array|false Array con la estructura completa de la exoneración o false si hay error + * + * Estructura de respuesta esperada: + * - numeroDocumento: string (ej: "AL-00028291-24") + * - identificacion: string (ej: "2300042155") + * - codigoProyectoCFIA: int + * - porcentajeExoneracion: int (ej: 13) + * - autorizacion: int (ej: 28291) + * - fechaEmision: string (ISO 8601 format) + * - fechaVencimiento: string (ISO 8601 format) + * - ano: int (ej: 2024) + * - cabys: array de strings con códigos CABYS + * - tipoAutorizacion: string (ej: "exoneracion") + * - tipoDocumento: array con 'codigo' y 'descripcion' + * - CodigoInstitucion: string (ej: "01") + * - nombreInstitucion: string + * - poseeCabys: bool + */ +function consultarExoneracionHacienda($numeroDocumento) +{ + // Convertir a minúsculas según requerimiento + $numeroDocumento = strtolower($numeroDocumento); + + // URL de la API de Hacienda + $url = 'https://api.hacienda.go.cr/fe/ex?autorizacion=' . urlencode($numeroDocumento); + + // Inicializar cURL + $ch = curl_init(); + + // Configurar opciones de cURL + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_HTTPHEADER => [ + 'Accept: application/json', + 'User-Agent: Factel-API/1.0' + ] + ]); + + // Ejecutar la petición + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + + curl_close($ch); + + // Verificar si hubo error en la petición + if ($error) { + error_log("Error consultando exoneración en Hacienda: " . $error); + return false; + } + + // Verificar código HTTP + if ($httpCode !== 200) { + error_log("Error HTTP " . $httpCode . " consultando exoneración: " . $numeroDocumento); + return false; + } + + // Decodificar respuesta JSON + $data = json_decode($response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + error_log("Error decodificando JSON de exoneración: " . json_last_error_msg()); + return false; + } + + // Validar que la respuesta contenga los campos esenciales + if (!isset($data['numeroDocumento']) || !isset($data['porcentajeExoneracion'])) { + error_log("Respuesta de API de Hacienda no contiene campos esperados para: " . $numeroDocumento); + return false; + } + + return $data; +} + /** * Actualiza los campos auto calculables usando los detalles de la linea * @@ -814,21 +1023,21 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= '' . $calculados[$l]['baseImponible'] . ''; if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { + foreach ($d->impuesto as $detalleImpuesto) { $xmlString .= ' - ' . $i->codigo . ''; + ' . $detalleImpuesto->codigo . ''; // Add if required if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) + isset($detalleImpuesto->codigo) && $detalleImpuesto->codigo == "99" && + isset($detalleImpuesto->codigoImpuestoOtro) && !empty($detalleImpuesto->codigoImpuestoOtro) ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; + $xmlString .= '' . $detalleImpuesto->codigoImpuestoOtro . ''; } // calcular el monto de impuesto si no se proporciona usando el codigo y la tarifa - if ($i->codigo === "01" && isset($i->codigoTarifa)) { + if ($detalleImpuesto->codigo === "01" && isset($detalleImpuesto->codigoTarifa)) { $impuestoPorTarifa = [ "01" => 0, // 0% (Artículo 32, num 1, RLIVA) @@ -843,27 +1052,28 @@ function genXMLGenerico($tipoDocumento = 'FE') "10" => 0, // Tarifa Exenta "11" => 0 // Tarifa 0% sin derecho a crédito ]; - $calculados[$l]['tarifa'] = $impuestoPorTarifa[$i->codigoTarifa] * 100; - $calculados[$l]['monto'] = round(floatval($calculados[$l]['baseImponible']) * $impuestoPorTarifa[$i->codigoTarifa], DECIMALES); + $calculados[$l]['tarifa'] = $impuestoPorTarifa[$detalleImpuesto->codigoTarifa] * 100; + + $calculados[$l]['monto'] = round(floatval($calculados[$l]['baseImponible']) * $impuestoPorTarifa[$detalleImpuesto->codigoTarifa], DECIMALES); } - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; + if (isset($detalleImpuesto->codigoTarifa) && $detalleImpuesto->codigoTarifa != "") { + $xmlString .= '' . $detalleImpuesto->codigoTarifa . ''; } $xmlString .= '' . $calculados[$l]['tarifa'] . ''; - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; + if (isset($detalleImpuesto->factorIVA) && $detalleImpuesto->factorIVA != "") { + $xmlString .= '' . $detalleImpuesto->factorIVA . ''; } if ( - isset($i->codigo) && - in_array($i->codigo, ["03", "04", "05", "06"]) && - isset($i->datosImpuestoEspecifico) && - is_object($i->datosImpuestoEspecifico) + isset($detalleImpuesto->codigo) && + in_array($detalleImpuesto->codigo, ["03", "04", "05", "06"]) && + isset($detalleImpuesto->datosImpuestoEspecifico) && + is_object($detalleImpuesto->datosImpuestoEspecifico) ) { - $datosImpuestoEsp = $i->datosImpuestoEspecifico; + $datosImpuestoEsp = $detalleImpuesto->datosImpuestoEspecifico; $xmlString .= ''; if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; @@ -883,95 +1093,128 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ''; } - if (!isset($i->monto)) { - $i->{"monto"} = $calculados[$l]['monto']; + if (!isset($detalleImpuesto->monto)) { + $detalleImpuesto->{"monto"} = $calculados[$l]['monto']; } else { - if ($i->monto != $calculados[$l]['monto']) { + if ($detalleImpuesto->monto != $calculados[$l]['monto']) { tools_reply("El monto del impuesto no coincide con el calculado para la línea $l, Calculado: " . $calculados[$l]['monto'], true); } } - $xmlString .= '' . $i->monto . ''; + // Procesar exoneración, si existe + $xmlStringExoneracion = ''; + if (isset($detalleImpuesto->exoneracion) && $detalleImpuesto->exoneracion != "") { + + // Validar exoneración con la API de Hacienda (opcional) + $datosExoneracion = consultarYMapearExoneracion($detalleImpuesto->exoneracion->numeroDocumento); + if ($datosExoneracion === false) { + tools_reply([ + "Status" => 400, + "text" => "Advertencia: No se pudo validar la exoneración en Hacienda " . $detalleImpuesto->exoneracion->numeroDocumento, + ], true); + } + $detalleImpuesto->exoneracion = (object) array_merge((array) $detalleImpuesto->exoneracion, (array) $datosExoneracion); + + // exonera lo mas que permita ser exonerado por el impuesto aplicado + $porcentajeExonerado = ($detalleImpuesto->exoneracion->tarifaExoneracion > $impuestoPorTarifa[$detalleImpuesto->codigoTarifa] * 100) ? $impuestoPorTarifa[$detalleImpuesto->codigoTarifa] * 100 : $detalleImpuesto->exoneracion->tarifaExoneracion; + $detalleImpuesto->exoneracion->montoExoneracion = $calculados[$l]['subTotal'] * ($porcentajeExonerado / 100); - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' + $xmlStringExoneracion .= ' - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; + ' . $detalleImpuesto->exoneracion->tipoDocumento . ''; + if (isset($detalleImpuesto->exoneracion->tipoDocumentoOtro) && !empty($detalleImpuesto->exoneracion->tipoDocumentoOtro)) { + $xmlStringExoneracion .= '' . $detalleImpuesto->exoneracion->tipoDocumentoOtro . ''; } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; + $xmlStringExoneracion .= '' . $detalleImpuesto->exoneracion->numeroDocumento . ''; + if (isset($detalleImpuesto->exoneracion->numeroArticulo) && !empty($detalleImpuesto->exoneracion->numeroArticulo)) { + $xmlStringExoneracion .= '' . $detalleImpuesto->exoneracion->numeroArticulo . ''; } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; + if (isset($detalleImpuesto->exoneracion->numeroInciso) && !empty($detalleImpuesto->exoneracion->numeroInciso)) { + $xmlStringExoneracion .= '' . $detalleImpuesto->exoneracion->numeroInciso . ''; } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; + $xmlStringExoneracion .= '' . $detalleImpuesto->exoneracion->nombreInstitucion . ''; + if (isset($detalleImpuesto->exoneracion->nombreInstitucionOtros) && !empty($detalleImpuesto->exoneracion->nombreInstitucionOtros)) { + $xmlStringExoneracion .= '' . $detalleImpuesto->exoneracion->nombreInstitucionOtros . ''; } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' + $xmlStringExoneracion .= ' + ' . $detalleImpuesto->exoneracion->fechaEmision . ' + ' . $detalleImpuesto->exoneracion->tarifaExoneracion . ' + ' . $detalleImpuesto->exoneracion->montoExoneracion . ' '; + } + $xmlString .= '' . $detalleImpuesto->monto . ''; + $xmlString .= $xmlStringExoneracion; $xmlString .= ''; - if (isset($i->codigo) && isset($i->monto)) { - // Se suma el impuesto neto a totalImpuestos - if (!isset($calculados[$l]['impuestoNeto'])) { - $calculados[$l]['impuestoNeto'] = 0; + if (isset($detalleImpuesto->codigo) && isset($detalleImpuesto->monto)) { + + foreach (['impuestoNeto'] as $key) { + if (!isset($calculados[$l][$key])) { + $calculados[$l][$key] = 0; + } } - $calculados[$l]['impuestoNeto'] += round($i->monto, DECIMALES); - if ($i->codigoTarifa === "10" || $i->codigoTarifa === "11") { - // Exento - $resumenKeys = [ - "servicio" => "totalServExentos", - "mercancia" => "totalMercanciasExentas", - ]; - $calculados['totalExento'] += ($calculados[$l]['montoTotal']); + foreach (['totalMercExonerada', 'totalServExonerado'] as $key) { + if (!isset($calculados[$key])) { + $calculados[$key] = 0; + } + } + + if (isset($detalleImpuesto->exoneracion) && isset($detalleImpuesto->exoneracion->montoExoneracion)) { + $calculados[$l]['impuestoNeto'] += round($detalleImpuesto->monto - $detalleImpuesto->exoneracion->montoExoneracion, DECIMALES); } else { - // Gravado - $resumenKeys = [ - "servicio" => "totalServGravados", - "mercancia" => "totalMercanciasGravadas", - ]; - $calculados['totalGravado'] += ($calculados[$l]['montoTotal']); + $calculados[$l]['impuestoNeto'] += round($detalleImpuesto->monto, DECIMALES); } - // Se suma el impuesto neto a totalImpuestos // 4.4 se basa en los cabys para saber que es un servicio o mercaderia, // los codigos que empiezan de 0 a 4 son mercaderias y los que empiezan de 5 a 9 son servicios - if (isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5', '6', '7', '8', '9'], true)) { - // Servicio - $calculados[$resumenKeys['servicio']] += ($calculados[$l]['montoTotal']); + $esServicio = (isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5', '6', '7', '8', '9'], true)); + $esExento = ($detalleImpuesto->codigoTarifa === "10" || $detalleImpuesto->codigoTarifa === "11"); + + if ($esExento) { + // Exento + $calculados[$esServicio ? "totalServExentos" : "totalMercanciasExentas"] += $calculados[$l]['montoTotal']; + $calculados['totalExento'] += $calculados[$l]['montoTotal']; + $calculados['totalImpuesto'] += $detalleImpuesto->monto; } else { - // Mercancia - $calculados[$resumenKeys['mercancia']] += ($calculados[$l]['montoTotal']); - } - $calculados['totalImpuesto'] += $i->monto; + // llave para el mapa de desglose de impuestos por código y tarifa + $keyDesgloseImpuesto = $detalleImpuesto->codigo . "-" . $detalleImpuesto->codigoTarifa; + if (!isset($calculados['totalDesgloseImpuesto'][$keyDesgloseImpuesto])) { + $montoDesgloseImpuesto = $detalleImpuesto->monto; + } else { + $montoDesgloseImpuesto = $calculados['totalDesgloseImpuesto'][$keyDesgloseImpuesto]->TotalMontoImpuesto + $detalleImpuesto->monto; + } - // Desglose de impuestos por código y tarifa - $tdiKey = $i->codigo . "-" . $i->codigoTarifa; + if (isset($detalleImpuesto->exoneracion) && isset($detalleImpuesto->exoneracion->montoExoneracion)) { + // hay monto exonerado + $montoExonerado = $porcentajeExonerado / $detalleImpuesto->exoneracion->tarifaExoneracion * $calculados[$l]['montoTotal']; + $calculados[$l]['montoTotal'] -= $montoExonerado; + $calculados['totalImpuesto'] += $detalleImpuesto->monto - $detalleImpuesto->exoneracion->montoExoneracion; + + $calculados['totalDesgloseImpuesto'][$keyDesgloseImpuesto] = (object) [ + "Codigo" => $detalleImpuesto->codigo, + "CodigoTarifaIVA" => $detalleImpuesto->codigoTarifa, + "TotalMontoImpuesto" => ($montoDesgloseImpuesto - $detalleImpuesto->exoneracion->montoExoneracion) + ]; + } else { + // no se exoneró nada + $calculados['totalImpuesto'] += $detalleImpuesto->monto; + $montoExonerado = 0; + + $calculados['totalDesgloseImpuesto'][$keyDesgloseImpuesto] = (object) [ + "Codigo" => $detalleImpuesto->codigo, + "CodigoTarifaIVA" => $detalleImpuesto->codigoTarifa, + "TotalMontoImpuesto" => $montoDesgloseImpuesto + ]; + } - if (!isset($calculados['totalDesgloseImpuesto'][$tdiKey])) { - $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ - "Codigo" => $i->codigo, - "CodigoTarifaIVA" => $i->codigoTarifa, - "TotalMontoImpuesto" => $i->monto - ]; - } else { - $_totalMontoImpuesto = $calculados['totalDesgloseImpuesto'][$tdiKey]->TotalMontoImpuesto; - $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ - "Codigo" => $i->codigo, - "CodigoTarifaIVA" => $i->codigoTarifa, - "TotalMontoImpuesto" => $i->monto + $_totalMontoImpuesto - ]; + $calculados[$esServicio ? "totalServExonerado" : "totalMercExonerada"] += $montoExonerado; + $calculados['totalExonerado'] += $montoExonerado; + $calculados[$esServicio ? "totalServGravados" : "totalMercanciasGravadas"] += $calculados[$l]['montoTotal']; + $calculados['totalGravado'] += $calculados[$l]['montoTotal']; } } } @@ -1051,10 +1294,6 @@ function genXMLGenerico($tipoDocumento = 'FE') // @TODO revisar no sujetos // $calculados['totalNoSujeto'] = $calculados['totalServNoSujetos'] + $calculados['totalMercanciasNoSujetas']; - // Se obtiene de la suma de los campos "total servicios exonerados de IVA" mas "total de mercancías exoneradas del IVA". - // @TODO revisar exoneraciones - // $calculados['totalExonerado'] = $calculados['totalServExonerados'] + $calculados['totalMercanciasExoneradas']; - // Calcular totalVenta // Se obtiene de la sumatoria de los campos “total gravado”, “total exento”, “Total Exonerado” y “Total No Sujeto $calculados['totalVenta'] = @@ -1151,8 +1390,8 @@ function genXMLGenerico($tipoDocumento = 'FE') $totalMediosPago += floatval($o->totalMedioPago); } - $t1 = round((float) $totalMediosPago, 2); - $t2 = round((float) $calculados['totalComprobante'], 2); + $t1 = round((float) $totalMediosPago, DECIMALES); + $t2 = round((float) $calculados['totalComprobante'], DECIMALES); if (abs($t1 - $t2) > 1) { tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [$t1], Total Comprobante: [$t2]", true); } diff --git a/api/tools/ValidadorXML.php b/api/tools/ValidadorXML.php index 96673df1..af2dc669 100644 --- a/api/tools/ValidadorXML.php +++ b/api/tools/ValidadorXML.php @@ -1,8 +1,21 @@ "$baseFolder/xsd/FacturaElectronica_V$version.xsd", "02" => "$baseFolder/xsd/FacturaCompra_V$version.xsd", @@ -55,21 +75,21 @@ public static function validateXml($contenido_xml, $consecutivo, $version = "4.4 if (empty($contenido_xml) || empty($tipo_doc)) { return (object) [ - "status" => "error", + "status" => "error", "message" => "Error: falta el contenido del XML o el tipo de documento ($tipo_doc) al validar.", ]; } if (!array_key_exists($tipo_doc, $schemas)) { return (object) [ - "status" => "error", + "status" => "error", "message" => "Error: no se tiene un esquema para el tipo $tipo_doc v$version", ]; } if (!file_exists($schemas[$tipo_doc])) { return (object) [ - "status" => "error", + "status" => "error", "message" => "Error: no existe el archivo XSD " . $schemas[$tipo_doc] . " definido para el tipo $tipo_doc, v$version", ]; } @@ -79,19 +99,50 @@ public static function validateXml($contenido_xml, $consecutivo, $version = "4.4 $xml->loadXML($contenido_xml); if (!$xml->schemaValidate($schemas[$tipo_doc])) { - $errors = ValidadorXML::libxml_display_errors(); + $errors = self::libxml_display_errors(); $r = [ - "status" => "error", - "schema" => $schemas[$tipo_doc], + "status" => "error", + "schema" => $schemas[$tipo_doc], "message" => $errors, - "xml" => $contenido_xml + "xml" => $contenido_xml ]; - if(count($errors) === 0) { + if (count($errors) === 0) { $r["status"] = "ok"; $r["message"] = "El XML es válido"; unset($r["xml"]); } return (object) $r; } + + return (object) [ + "status" => "ok", + "message" => "El XML es válido", + ]; + } + /** + * Get the list of supported document types + * + * @return array + */ + public static function getSupportedDocumentTypes(): array + { + return [ + "01" => "Factura Electrónica", + "02" => "Factura de Compra", + "03" => "Nota de Crédito Electrónica", + "04" => "Tiquete Electrónico", + "05" => "Mensaje Hacienda", + ]; + } + + /** + * Get the document type from consecutivo + * + * @param string $consecutivo + * @return string + */ + public static function getDocumentTypeFromConsecutivo(string $consecutivo): string + { + return substr($consecutivo, 8, 2); } } From ad49b61122406988f0f309e2b6aa8dd38d03ae75 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 2 Jan 2026 13:57:11 -0500 Subject: [PATCH 29/30] cambios en el genxml --- api/contrib/genXML/genXML.php | 10 +- api/contrib/genXML/genXML.txt | 4755 --------------------------------- 2 files changed, 5 insertions(+), 4760 deletions(-) delete mode 100644 api/contrib/genXML/genXML.txt diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index eb96e8e0..427159b3 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -1093,12 +1093,12 @@ function genXMLGenerico($tipoDocumento = 'FE') $xmlString .= ''; } - if (!isset($detalleImpuesto->monto)) { + if (!isset($detalleImpuesto->monto) || ($detalleImpuesto->monto === 0 && $calculados[$l]['monto'] != 0)) { $detalleImpuesto->{"monto"} = $calculados[$l]['monto']; - } else { - if ($detalleImpuesto->monto != $calculados[$l]['monto']) { - tools_reply("El monto del impuesto no coincide con el calculado para la línea $l, Calculado: " . $calculados[$l]['monto'], true); - } + } + + if ($detalleImpuesto->monto === 0 && $detalleImpuesto->monto != $calculados[$l]['monto']) { + tools_reply("El monto del impuesto no coincide con el calculado para la línea $l, Monto: " . $detalleImpuesto->monto . ", Calculado: " . $calculados[$l]['monto'], true); } // Procesar exoneración, si existe diff --git a/api/contrib/genXML/genXML.txt b/api/contrib/genXML/genXML.txt deleted file mode 100644 index 9bb1b03c..00000000 --- a/api/contrib/genXML/genXML.txt +++ /dev/null @@ -1,4755 +0,0 @@ - - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/* * ************************************************** */ -/* Constantes de validacion */ -/* * ************************************************** */ -define("DECIMALES", 5); -define("TIPODOCREFVALUES", array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '99')); -define('CODIDOREFVALUES', array('01', '02', '04', '05', '06', '07', '08', '09', '10', '11', '12', '99')); - -const CODIGOACTIVIDADSIZE = 6; -const EMISORNOMBREMAXSIZE = 100; -const EMISORNUMEROTELMIN = 8; -const EMISORNUMEROTELMAX = 20; -const RECEPTORNOMBREMAXSIZE = 100; -const RECEPTOROTRASSENASMAXSIZE = 250; -const RECEPTOROTRASSENASEXTRANJEROMAXSIZE = 300; -const EMAIL_REGEX = "/^\s*\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\s*$/"; - -const AUTO_CALCULAR = ["totalServGravados", "totalServExentos", "totalServExonerado", "totalServNoSujeto", "totalMercanciasGravadas", "totalMercanciasExentas", "totalMercExonerada", "totalMercNoSujeta", "totalGravado", "totalExento", "totalExonerado", "totalNoSujeto", "totalVenta", "totalDescuentos", "totalVentaNeta", "totalDesgloseImpuesto", "totalImpuesto"]; - -tools_useTool('ValidadorXML.php'); - -/** - * Devuelve true si todas las claves existen en el array - * - function array_all(array $keys, array $array) { - foreach ($keys as $key) { - if (!array_key_exists($key, $array)) { - return false; - } - } - return true; - } - - * Devuelve true si al menos una de las claves existe en el array - * -function array_any(array $keys, array $array) { - foreach ($keys as $key) { - if (array_key_exists($key, $array)) { - return true; - } - } - return false; -} */ - -/** - * Valida las reglas conocidas de una linea del detalle para evitar rechazos - * - * @param int $numLinea - * @param object $detallesLinea - * @return void - */ -function validarLinea($numLinea, $d) -{ - $d = (array) $d; - - if (isset($d['baseImponible']) && !isset($d['impuesto'])) { - tools_reply([ - "Status" => 400, - "text" => "Detalle de líneas $numLinea: cuando se envía el campo 'baseImponible' es obligatorio enviar también el campo 'impuesto'", - ], true); - } -} - -/** - * Actualiza los campos auto calculables usando los detalles de la linea - * - * @param int $numLinea - * @param object $detallesLinea - * @param array &$calculados - * @return void - */ -function autoCalcularLinea($numLinea, &$d, $totalDescuentos, &$calculados = []) -{ - - if (!isset($calculados['totalDescuentos'])) { - $calculados['totalDescuentos'] = 0; - } - $calculados['totalDescuentos'] += round($totalDescuentos, DECIMALES); - - if (!isset($calculados[$numLinea])) { - $calculados[$numLinea] = []; - } - - // Se obtiene de la resta del campo monto total menos monto de descuento concedido - $calculados[$numLinea]['montoTotal'] = round($d->precioUnitario * $d->cantidad, DECIMALES); - $calculados[$numLinea]['subTotal'] = round($calculados[$numLinea]['montoTotal'] - $totalDescuentos, DECIMALES); - - /* - @TODO base imponible puede incluir - Este campo será de condición obligatoria, cuando el producto/ servicio este gravado con algún impuesto. - Se obtiene de la suma entre el campo "Subtotal", más - el impuesto selectivo de consumo (02), - el Impuesto específico de Bebidas Alcohólicas (04), - el Impuesto Específico sobre las bebidas envasadas sin contenido alcohólico y jabones de tocador (05) y - el impuesto al cemento (12), cuando corresponda. - Este campo se podrá editar cuando se seleccione - en el campo "IVA cobrado a nivel de fábrica" el Código 01 - o en el campo de "Código del impuesto" el código 07. - */ - - $calculados[$numLinea]['baseImponible'] = $calculados[$numLinea]['subTotal']; - - foreach (AUTO_CALCULAR as $field) { - if (!isset($calculados[$field])) { - if ($field !== 'totalDesgloseImpuesto') { - $calculados[$field] = 0; - continue; - } - $calculados[$field] = []; - } - } - -} - -/** - * Elimina caracteres no permitidos - * Solo letras, numeros, espacios y signos de puntuacion comunes en detalle - */ -function limpiarTexto($texto, $largoMaximo = FALSE) -{ - $limpio = preg_replace('/[^\\p{L}\\p{N}\\s.,;:\\-\\_\\(\\)\\[\\]\\{\\}\\!\\?\'"\\n\\r]/u', '', $texto); - - if ($largoMaximo === FALSE) { - return $limpio; - } - // Trunca a la longitud máxima permitida - return mb_substr($limpio, 0, $largoMaximo); -} - -/* * ************************************************** */ -/* Funcion para generar XML */ -/* * ************************************************** */ - -function genXMLFe() -{ - return genXMLGenerico('FE'); -} - -function genXMLTe() -{ - return genXMLGenerico('TE'); -} - -function genXMLNc() -{ - return genXMLGenerico('NC'); -} - -function genXMLNd() -{ - return genXMLGenerico('ND'); -} - -function genXMLGenerico($tipoDocumento = 'FE') -{ - // Datos contribuyente - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); - // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $codigoActividadReceptor = params_get("codigo_actividad_receptor"); - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - // Datos emisor - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - // This API only supports one email address for emisor - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - // Datos receptor - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorProvincia = params_get("receptor_provincia"); - $receptorCanton = params_get("receptor_canton"); - $receptorDistrito = params_get("receptor_distrito"); - $receptorBarrio = params_get("receptor_barrio"); - $receptorOtrasSenas = params_get("receptor_otras_senas"); - $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - // Detalles de tiquete / Factura - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - $totalIVADevuelto = params_get("totalIVADevuelto"); - $totalOtrosCargos = params_get("totalOtrosCargos"); - - $otros = json_decode(params_get('otros')); - - // Detalles de la compra - $detalles = json_decode(params_get("detalles")); - $informacionReferencia = json_decode(params_get("informacion_referencia")); - $otrosCargos = json_decode(params_get("otrosCargos")); - $mediosPago = json_decode(params_get("medios_pago")); - - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - grace_debug(params_get("detalles")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($mediosPago) && $mediosPago != "") { - grace_debug(params_get("medios_pago")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); - } - - if (isset($otrosCargos) && $otrosCargos != "") { - if (count($otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); - //Delimita el array a solo 15 elementos - $otrosCargos = array_slice($otrosCargos, 0, 15); - } - } - - if (isset($mediosPago) && $mediosPago != "") { - if (count($mediosPago) > 4) { - error_log("mediosPago: " . count($mediosPago) . " is greater than 4"); - //Delimita el array a solo 4 elementos - $mediosPago = array_slice($mediosPago, 0, 4); - } - } else { - if (!in_array($condVenta, ['02', '08', '10'], true)) { - tools_reply([ - "Status" => 400, - "text" => [ - "medios_pago" => $mediosPago, - "mensaje" => "El campo 'medios_pago' es obligatorio cuando 'condicion_venta' no es '02', '08' o '10'" - ] - ], true); - } - } - - $xmlClosingTag = ''; - - switch ($tipoDocumento) { - case 'FE': - $xmlString = ' - '; - $xmlClosingTag = ''; - break; - case 'TE': - $xmlString = ' - '; - $xmlClosingTag = ''; - break; - case 'NC': - $xmlString = ' - '; - $xmlClosingTag = ''; - break; - case 'ND': - $xmlString = ' - '; - $xmlClosingTag = ''; - break; - default: - tools_reply([ - "Status" => 400, - "text" => "El campo 'tipoDocumento' debe ser uno de los siguientes valores: FE, TE, NC, ND" - ], true); - } - - $xmlString .= '' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ''; - - if ($tipoDocumento == "FE" && isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { - $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); - } - - $xmlString .= ' - ' . $codigoActividadReceptor . ''; - } - - $xmlString .= ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito) { - $xmlString .= ' - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - $xmlString .= '123456'; - - $xmlString .= ''; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); - } - - $xmlString .= ' - ' . $receptorNombre . ''; - - if (!empty($receptorTipoIdentif) && !empty($receptorNumIdentif)) { - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - } - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= '' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' - . $receptorOtrasSenasExtranjero . - ''; - } - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - } - - $xmlString .= ''; - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } else { - // es credito segun la condicion de venta - if ($condVenta == "02") { - tools_reply([ - "Status" => 400, - "text" => "El campo 'plazoCredito' es obligatorio cuando 'condicionVenta' es '02'" - ], true); - } - } - - $xmlString .= ' - '; - - /* EJEMPLO DE DETALLES - [ - { - "codigoCABYS": "101010101", - "codigoComercial": [ - { "tipo": "01", "codigo": "A123" }, - { "tipo": "02", "codigo": "B456" } - ], - "cantidad": 2, - "unidadMedida": "Unid", - "tipoTransaccion": "Venta", - "unidadMedidaComercial": "Caja", - "detalle": "Medicamento genérico", - "numeroVINoSerie": "VIN123456789", - "registroMedicamento": "REG-CR-2024-0001", - "formaFarmaceutica": "TAB", - "detalleSurtido": [ - { - "codigoCABYSSurtido": "202020202", - "codigoComercialSurtido": [ - { "tipoSurtido": "01", "codigoSurtido": "S123" } - ], - "cantidadSurtido": 1, - "unidadMedidaSurtido": "Unid", - "unidadMedidaComercialSurtido": "Blister", - "detalleSurtido": "Surtido de medicamento", - "precioUnitarioSurtido": 120.00, - "montoTotalSurtido": 120.00, - "descuentoSurtido": [ - { - "montoDescuentoSurtido": 10.00, - "codigoDescuentoSurtido": "01", - "descuentoSurtidoOtros": "Descuento especial" - } - ], - "subTotalSurtido": 110.00, - "ivaCobradoFabricaSurtido": 5.00, - "baseImponibleSurtido": 105.00, - "impuestoSurtido": [ - { - "codigoImpuestoSurtido": "01", - "codigoTarifaIVASurtido": "08", - "tarifaSurtido": 13.00, - "montoImpuestoSurtido": 13.65, - "datosImpuestoEspecificoSurtido": { - "cantidadUnidadMedidaSurtido": 1, - "porcentajeSurtido": 5.0, - "proporcionSurtido": 0.5, - "volumenUnidadConsumoSurtido": 0.1, - "impuestoUnidadSurtido": 2.00 - } - } - ] - } - ], - "precioUnitario": 150.00, - "montoTotal": 300.00, - "descuento": [ - { - "montoDescuento": 20.00, - "codigoDescuento": "99", - "codigoDescuentoOTRO": "DESC-OTRO-001", - "naturalezaDescuento": "Descuento por promoción" - } - ], - "subTotal": 280.00, - "IVACobradoFabrica": 10.00, - "baseImponible": 270.00, - "impuesto": [ - { - "codigo": "01", - "codigoTarifa": "08", - "tarifa": 13.00, - "factorIVA": 1.0, - "monto": 35.10, - "exoneracion": { - "tipoDocumento": "01", - "tipoDocumentoOtro": "OTRODOC", - "numeroDocumento": "EXON-2024-001", - "numeroArticulo": "ART-01", - "numeroInciso": "INC-01", - "nombreInstitucion": "Ministerio de Salud", - "nombreInstitucionOtros": "Otra Institución", - "fechaEmision": "2024-06-01", - "tarifaExoneracion": 50.0, - "montoExoneracion": 17.55 - } - }, - { - "codigo": "03", - "codigoTarifa": "01", - "tarifa": 2.00, - "factorIVA": 0.5, - "monto": 5.00, - "datosImpuestoEspecifico": { - "cantidadUnidadMedida": 2, - "porcentaje": 10.0, - "proporcion": 0.2, - "volumenUnidadConsumo": 0.5, - "impuestoUnidad": 1.00 - } - } - ], - "impuestoAsumidoEmisorFabrica": 2.00, - "impuestoNeto": 22.55, - "montoTotalLinea": 302.55 - }, - { - "codigoCABYS": "303030303", - "cantidad": 1, - "unidadMedida": "Kg", - "detalle": "Producto sin surtido ni descuentos", - "precioUnitario": 50.00, - "montoTotal": 50.00, - "subTotal": 50.00, - "baseImponible": 50.00, - "impuesto": [ - { - "codigo": "01", - "codigoTarifa": "08", - "tarifa": 13.00, - "monto": 6.50 - } - ], - "montoTotalLinea": 56.50 - } - ] - */ - - $l = 1; - $calculados = []; - - foreach ($detalles as $d) { - - foreach (["codigoCABYS"] as $requiredField) { - if (!isset($d->{$requiredField}) || $d->{$requiredField} === '') { - tools_reply("Se requiere el campo $requiredField en el detalle #$l", true); - } - } - - $xmlString .= ' - - ' . $l . ''; - - $xmlString .= ' - ' . $d->codigoCABYS . ''; - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - - $d->detalle = limpiarTexto($d->detalle, 150); - - $xmlString .= '' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { - $xmlString .= ''; - $lineas = array_slice($d->detalleSurtido, 0, 20); - foreach ($lineas as $linea) { - $xmlString .= ''; - $xmlString .= '' . $linea->codigoCABYSSurtido . ''; - if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { - $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); - foreach ($codigos as $codigo) { - $xmlString .= ''; - $xmlString .= '' . $codigo->tipoSurtido . ''; - $xmlString .= '' . $codigo->codigoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->cantidadSurtido . ''; - $xmlString .= '' . $linea->unidadMedidaSurtido . ''; - if (isset($linea->unidadMedidaComercialSurtido)) { - $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; - } - $xmlString .= '' . $linea->detalleSurtido . ''; - $xmlString .= '' . $linea->precioUnitarioSurtido . ''; - $xmlString .= '' . $linea->montoTotalSurtido . ''; - if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { - $descuentos = array_slice($linea->descuentoSurtido, 0, 5); - foreach ($descuentos as $desc) { - $xmlString .= ''; - $xmlString .= '' . $desc->montoDescuentoSurtido . ''; - $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; - if (isset($desc->descuentoSurtidoOtros)) { - $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; - } - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->subTotalSurtido . ''; - if (isset($linea->ivaCobradoFabricaSurtido)) { - $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; - } - $xmlString .= '' . $linea->baseImponibleSurtido . ''; - if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { - $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); - foreach ($impuestos as $imp) { - $xmlString .= ''; - $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; - if (isset($imp->codigoImpuestoOTROSurtido)) { - $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; - } - if (isset($imp->codigoTarifaIVASurtido)) { - $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; - } - if (isset($imp->tarifaSurtido)) { - $xmlString .= '' . $imp->tarifaSurtido . ''; - } - if (isset($imp->datosImpuestoEspecificoSurtido)) { - $e = $imp->datosImpuestoEspecificoSurtido; - $xmlString .= ''; - if (isset($e->cantidadUnidadMedidaSurtido)) { - $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; - } - if (isset($e->porcentajeSurtido)) { - $xmlString .= '' . $e->porcentajeSurtido . ''; - } - if (isset($e->proporcionSurtido)) { - $xmlString .= '' . $e->proporcionSurtido . ''; - } - if (isset($e->volumenUnidadConsumoSurtido)) { - $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; - } - if (isset($e->impuestoUnidadSurtido)) { - $xmlString .= '' . $e->impuestoUnidadSurtido . ''; - } - $xmlString .= ''; - } - $xmlString .= '' . $imp->montoImpuestoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= ''; - } - $xmlString .= ''; - } - - $totalDescuentos = 0; - $xmlStringDescuento = ''; - // Procesar descuentos, si existen - if (isset($d->descuento) && !empty($d->descuento)) { - - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $c['montoDescuento'] = round(floatval($c['montoDescuento']), DECIMALES) * $d->cantidad; - $xmlStringDescuento = ' - ' . $c['montoDescuento'] . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlStringDescuento .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlStringDescuento .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlStringDescuento .= ''; - $totalDescuentos += $c['montoDescuento']; - } - } - } - - // Calcular los valores que se pueden derivar de los datos ingresados - autoCalcularLinea($l, $d, $totalDescuentos, $calculados); - - $xmlString .= '' . round(floatval($d->precioUnitario), DECIMALES) . ''; - $xmlString .= '' . $calculados[$l]['montoTotal'] . ''; - $xmlString .= $xmlStringDescuento; - $xmlString .= '' . $calculados[$l]['subTotal'] . ''; - - if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { - $xmlString .= '' . $d->IVACobradoFabrica . ''; - } - - $xmlString .= '' . $calculados[$l]['baseImponible'] . ''; - - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - - ' . $i->codigo . ''; - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - // calcular el monto de impuesto si no se proporciona usando el codigo y la tarifa - if ($i->codigo === "01" && isset($i->codigoTarifa)) { - - $impuestoPorTarifa = [ - "01" => 0, // 0% (Artículo 32, num 1, RLIVA) - "02" => 0.01, // Tarifa reducida 1% - "03" => 0.02, // Tarifa reducida 2% - "04" => 0.04, // Tarifa reducida 4% - "05" => 0, // Transitorio 0% - "06" => 0.04, // Transitorio 4% - "07" => 0.08, // Tarifa transitoria 8% - "08" => 0.13, // Tarifa general 13% - "09" => 0.05, // Tarifa reducida 0.5% - "10" => 0, // Tarifa Exenta - "11" => 0 // Tarifa 0% sin derecho a crédito - ]; - $calculados[$l]['tarifa'] = $impuestoPorTarifa[$i->codigoTarifa] * 100; - $calculados[$l]['monto'] = round(floatval($calculados[$l]['baseImponible']) * $impuestoPorTarifa[$i->codigoTarifa], DECIMALES); - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - $xmlString .= '' . $calculados[$l]['tarifa'] . ''; - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - if ( - isset($i->codigo) && - in_array($i->codigo, ["03", "04", "05", "06"]) && - isset($i->datosImpuestoEspecifico) && - is_object($i->datosImpuestoEspecifico) - ) { - $datosImpuestoEsp = $i->datosImpuestoEspecifico; - $xmlString .= ''; - if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { - $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; - } - if (isset($datosImpuestoEsp->porcentaje)) { - $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; - } - if (isset($datosImpuestoEsp->proporcion)) { - $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; - } - if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { - $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; - } - if (isset($datosImpuestoEsp->impuestoUnidad)) { - $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; - } - $xmlString .= ''; - } - - if (!isset($i->monto)) { - $i->{"monto"} = $calculados[$l]['monto']; - } else { - if ($i->monto != $calculados[$l]['monto']) { - tools_reply("El monto del impuesto no coincide con el calculado para la línea $l, Calculado: " . $calculados[$l]['monto'], true); - } - } - - $xmlString .= '' . $i->monto . ''; - - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' - - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; - } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; - } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; - } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; - } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' - '; - } - - $xmlString .= ''; - - if (isset($i->codigo) && isset($i->monto)) { - // Se suma el impuesto neto a totalImpuestos - if (!isset($calculados[$l]['impuestoNeto'])) { - $calculados[$l]['impuestoNeto'] = 0; - } - $calculados[$l]['impuestoNeto'] += round($i->monto, DECIMALES); - - if ($i->codigoTarifa === "10" || $i->codigoTarifa === "11") { - // Exento - $resumenKeys = [ - "servicio" => "totalServExentos", - "mercancia" => "totalMercanciasExentas", - ]; - $calculados['totalExento'] += ($calculados[$l]['montoTotal']); - } else { - // Gravado - $resumenKeys = [ - "servicio" => "totalServGravados", - "mercancia" => "totalMercanciasGravadas", - ]; - $calculados['totalGravado'] += ($calculados[$l]['montoTotal']); - } - - // Se suma el impuesto neto a totalImpuestos - // 4.4 se basa en los cabys para saber que es un servicio o mercaderia, - // los codigos que empiezan de 0 a 4 son mercaderias y los que empiezan de 5 a 9 son servicios - if (isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5', '6', '7', '8', '9'], true)) { - // Servicio - $calculados[$resumenKeys['servicio']] += ($calculados[$l]['montoTotal']); - } else { - // Mercancia - $calculados[$resumenKeys['mercancia']] += ($calculados[$l]['montoTotal']); - } - - $calculados['totalImpuesto'] += $i->monto; - - // Desglose de impuestos por código y tarifa - $tdiKey = $i->codigo . "-" . $i->codigoTarifa; - - if (!isset($calculados['totalDesgloseImpuesto'][$tdiKey])) { - $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ - "Codigo" => $i->codigo, - "CodigoTarifaIVA" => $i->codigoTarifa, - "TotalMontoImpuesto" => $i->monto - ]; - } else { - $_totalMontoImpuesto = $calculados['totalDesgloseImpuesto'][$tdiKey]->TotalMontoImpuesto; - $calculados['totalDesgloseImpuesto'][$tdiKey] = (object) [ - "Codigo" => $i->codigo, - "CodigoTarifaIVA" => $i->codigoTarifa, - "TotalMontoImpuesto" => $i->monto + $_totalMontoImpuesto - ]; - } - } - } - } - - if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { - $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; - } else { - $xmlString .= '0'; - } - - $xmlString .= '' . $calculados[$l]['impuestoNeto'] . ''; - - // Se calcula de la siguiente manera: se obtiene de la sumatoria de los campos “Subtotal”, “Impuesto Neto”. - $calculados[$l]['montoTotalLinea'] = $calculados[$l]['subTotal'] + $calculados[$l]['impuestoNeto']; - - $xmlString .= '' . $calculados[$l]['montoTotalLinea'] . ''; - $xmlString .= ''; - - validarLinea($l, $d); - - $l++; - } - - $xmlString .= ''; - - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - foreach ($calculados as $key => $value) { - if (is_numeric($value)) { - $calculados[$key] = round($value, DECIMALES); - } - } - - $xmlString .= ' - '; - - $xmlString .= ' - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - - // Se obtiene de la suma de los campos "Total servicios No Sujetos de IVA" mas "Total mercancías No Sujetas de IVA". - // @TODO revisar no sujetos - // $calculados['totalNoSujeto'] = $calculados['totalServNoSujetos'] + $calculados['totalMercanciasNoSujetas']; - - // Se obtiene de la suma de los campos "total servicios exonerados de IVA" mas "total de mercancías exoneradas del IVA". - // @TODO revisar exoneraciones - // $calculados['totalExonerado'] = $calculados['totalServExonerados'] + $calculados['totalMercanciasExoneradas']; - - // Calcular totalVenta - // Se obtiene de la sumatoria de los campos “total gravado”, “total exento”, “Total Exonerado” y “Total No Sujeto - $calculados['totalVenta'] = - $calculados['totalGravado'] + - $calculados['totalExento'] + - $calculados['totalExonerado'] + - $calculados['totalNoSujeto']; - - // Calcular totalVentaNeta - // Se obtiene de la resta de los campos total venta menos total descuento - $calculados['totalVentaNeta'] = $calculados['totalVenta'] - $calculados['totalDescuentos']; - - // totalComprobante: Se obtiene de la suma de los campos "total venta neta", "monto total del impuesto" y "total otros cargos" menos "total IVA devuelto", en caso de contar con dichos campos. - $calculados['totalComprobante'] = $calculados['totalVentaNeta'] + $calculados['totalImpuesto']; - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $calculados['totalComprobante'] += $totalOtrosCargos; - } - if ($totalIVADevuelto != '') { - $calculados['totalComprobante'] -= $totalIVADevuelto; - } - - // agrega los campos del resumen que esten en el array calculados y no esten vacios - foreach (AUTO_CALCULAR as $campoResumen) { - if ($campoResumen == "totalDesgloseImpuesto") { - // Add logic for TotalDesgloseImpuesto - $totalDesgloseImpuesto = $calculados['totalDesgloseImpuesto'] ?? []; - if (count($totalDesgloseImpuesto) > 0) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . round($impuesto->TotalMontoImpuesto, DECIMALES) . ''; - } - $xmlString .= ''; - } - } - continue; - } - - if ($calculados[$campoResumen] != '') { - $xmlString .= '<' . ucfirst($campoResumen) . '>' . $calculados[$campoResumen] . ''; - } - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if ($totalIVADevuelto != '') { - $xmlString .= ' - ' . $totalIVADevuelto . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - $totalMediosPago = 0; - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . round($o->totalMedioPago, DECIMALES) . ''; - } - - $xmlString .= ''; - $totalMediosPago += floatval($o->totalMedioPago); - } - - $t1 = round((float) $totalMediosPago, 2); - $t2 = round((float) $calculados['totalComprobante'], 2); - if (abs($t1 - $t2) > 1) { - tools_reply("El total de los medios de pago no coincide con el total del comprobante. Total Medios de Pago: [$t1], Total Comprobante: [$t2]", true); - } - } - - $xmlString .= '' . $calculados['totalComprobante'] . ''; - - $xmlString .= ' '; - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - // Serialize structured content as JSON string, or use a plain string - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado)) { - // Convert object/array to JSON string - $contenido = htmlspecialchars(json_encode($otroContenido->contenidoEstructurado, JSON_UNESCAPED_UNICODE)); - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - // Cierre del XML - $xmlString .= $xmlClosingTag; - - // eliminar espacios en blanco para reducir el tamaño del XML - $xmlString = preg_replace('/>\s+<', $xmlString); - - $validacion = ValidadorXML::validateXml($xmlString, $consecutivo); - - if ($validacion && $validacion->status == "error") { - if (conf_get('mode', 'core', 'web') == 'cli') { - return tools_reply([ - "Status" => 400, - "text" => array( - "clave" => $clave, - "validacion" => $validacion - ) - ], true); - } - - tools_reply([ - "Status" => 400, - "text" => array( - "clave" => $clave, - "validacion" => $validacion - ) - ], true); - } - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); -} - -/* * ************************************************** */ -/* Funcion de prueba */ -/* * ************************************************** */ - -function test() -{ - return "Esto es un test"; -} - - - -function old_genXMLNC() -{ - - // Datos contribuyente - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $codigoActividadReceptor = params_get("codigo_actividad_receptor"); - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - // Datos emisor - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - // Datos receptor - $omitir_receptor = params_get("omitir_receptor"); // Deprecated - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorProvincia = params_get("receptor_provincia"); - $receptorCanton = params_get("receptor_canton"); - $receptorDistrito = params_get("receptor_distrito"); - $receptorBarrio = params_get("receptor_barrio"); - $receptorOtrasSenas = params_get("receptor_otras_senas"); - $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - // Detalles de tiquete / Factura - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerado = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravado = params_get("total_gravado"); - $totalExento = params_get("total_exento"); - $totalExonerado = params_get("total_exonerado"); - $totalNoSujeto = params_get("total_no_sujeto"); - $totalVenta = params_get("total_venta"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - $totalIVADevuelto = params_get("totalIVADevuelto"); - $totalOtrosCargos = params_get("totalOtrosCargos"); - // $totalComprobante = params_get("total_comprobante"); - - $otros = json_decode(params_get('otros')); - - // Detalles de la compra - $detalles = json_decode(params_get("detalles")); - $informacionReferencia = json_decode(params_get("informacion_referencia")); - $otrosCargos = json_decode(params_get("otrosCargos")); - $mediosPago = json_decode(params_get("medios_pago")); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($mediosPago) && $mediosPago != "") { - grace_debug(params_get("medios_pago")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); - } - - if (isset($otrosCargos) && $otrosCargos != "") { - if (count($otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); - //Delimita el array a solo 15 elementos - $otrosCargos = array_slice($otrosCargos, 0, 15); - } - } - - if (isset($mediosPago) && $mediosPago != "") { - if (count($mediosPago) > 4) { - error_log("otrosCargos: " . count($mediosPago) . " is greater than 4"); - //Delimita el array a solo 4 elementos - $mediosPago = array_slice($mediosPago, 0, 4); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ''; - - if (isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { - $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); - } - - $xmlString .= ' - ' . $codigoActividadReceptor . ''; - } - - $xmlString .= ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - if ($emisorOtrasSenas != '') { - $xmlString .= '' . $emisorOtrasSenas . ''; - } - $xmlString .= ''; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - return tools_reply([ - "Status" => 400, - "text" => "El correo del emisor no cumple con el formato establecido." - ], true); - } - - if ($omitir_receptor != 'true') { - $xmlString .= ' - ' . $receptorNombre . ''; - - if ($receptorTipoIdentif != '' && $receptorNumIdentif != '') { - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - } - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= ' - ' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' - . $receptorOtrasSenasExtranjero . - ''; - } - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - } - - $xmlString .= ''; - } - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ' - '; - - /* EJEMPLO DE DETALLES - { - "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], - "2":["1","Sp","Honorarios","100000","100000","100000","100000"] - } - */ - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - ' . $l . ''; - - if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { - $xmlString .= '' . $d->partidaArancelaria . ''; - } - - if (isset($d->codigoCABYS) && $d->codigoCABYS != "") { - $xmlString .= ' - ' . $d->codigoCABYS . ''; - } - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { - $xmlString .= ''; - $lineas = array_slice($d->detalleSurtido, 0, 20); - foreach ($lineas as $linea) { - $xmlString .= ''; - $xmlString .= '' . $linea->codigoCABYSSurtido . ''; - if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { - $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); - foreach ($codigos as $codigo) { - $xmlString .= ''; - $xmlString .= '' . $codigo->tipoSurtido . ''; - $xmlString .= '' . $codigo->codigoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->cantidadSurtido . ''; - $xmlString .= '' . $linea->unidadMedidaSurtido . ''; - if (isset($linea->unidadMedidaComercialSurtido)) { - $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; - } - $xmlString .= '' . $linea->detalleSurtido . ''; - $xmlString .= '' . $linea->precioUnitarioSurtido . ''; - $xmlString .= '' . $linea->montoTotalSurtido . ''; - if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { - $descuentos = array_slice($linea->descuentoSurtido, 0, 5); - foreach ($descuentos as $desc) { - $xmlString .= ''; - $xmlString .= '' . $desc->montoDescuentoSurtido . ''; - $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; - if (isset($desc->descuentoSurtidoOtros)) { - $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; - } - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->subTotalSurtido . ''; - if (isset($linea->ivaCobradoFabricaSurtido)) { - $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; - } - $xmlString .= '' . $linea->baseImponibleSurtido . ''; - if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { - $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); - foreach ($impuestos as $imp) { - $xmlString .= ''; - $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; - if (isset($imp->codigoImpuestoOTROSurtido)) { - $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; - } - if (isset($imp->codigoTarifaIVASurtido)) { - $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; - } - if (isset($imp->tarifaSurtido)) { - $xmlString .= '' . $imp->tarifaSurtido . ''; - } - if (isset($imp->datosImpuestoEspecificoSurtido)) { - $e = $imp->datosImpuestoEspecificoSurtido; - $xmlString .= ''; - if (isset($e->cantidadUnidadMedidaSurtido)) { - $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; - } - if (isset($e->porcentajeSurtido)) { - $xmlString .= '' . $e->porcentajeSurtido . ''; - } - if (isset($e->proporcionSurtido)) { - $xmlString .= '' . $e->proporcionSurtido . ''; - } - if (isset($e->volumenUnidadConsumoSurtido)) { - $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; - } - if (isset($e->impuestoUnidadSurtido)) { - $xmlString .= '' . $e->impuestoUnidadSurtido . ''; - } - $xmlString .= ''; - } - $xmlString .= '' . $imp->montoImpuestoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= ''; - } - $xmlString .= ''; - } - - $xmlString .= ' - ' . round(floatval($d->precioUnitario), DECIMALES) . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . round(floatval($c['montoDescuento']), DECIMALES) . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { - $xmlString .= '' . $d->IVACobradoFabrica . ''; - } - - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - ' . $i->codigo . ''; - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - if ( - isset($i->codigo) && - in_array($i->codigo, ["03", "04", "05", "06"]) && - isset($i->datosImpuestoEspecifico) && - is_object($i->datosImpuestoEspecifico) - ) { - $datosImpuestoEsp = $i->datosImpuestoEspecifico; - $xmlString .= ''; - if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { - $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; - } - if (isset($datosImpuestoEsp->porcentaje)) { - $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; - } - if (isset($datosImpuestoEsp->proporcion)) { - $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; - } - if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { - $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; - } - if (isset($datosImpuestoEsp->impuestoUnidad)) { - $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; - } - $xmlString .= ''; - } - - $xmlString .= '' . $i->monto . ''; - - if (isset($i->montoExportacion) && $i->montoExportacion != "") { - $xmlString .= '' . $i->montoExportacion . ''; - } - - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' - - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; - } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; - } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; - } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; - } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' - '; - } - - $xmlString .= ''; - } - } - if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { - $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; - } - $xmlString .= '' . $d->impuestoNeto . ''; - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerado != '') { - $xmlString .= ' - ' . $totalServExonerado . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if ($totalIVADevuelto != '') { - $xmlString .= ' - ' . $totalIVADevuelto . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} - -function old_genXMLND() -{ - - // Datos contribuyente - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $codigoActividadReceptor = params_get("codigo_actividad_receptor"); - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - // Datos emisor - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - // Datos receptor - $omitir_receptor = params_get("omitir_receptor"); // Deprecated - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorProvincia = params_get("receptor_provincia"); - $receptorCanton = params_get("receptor_canton"); - $receptorDistrito = params_get("receptor_distrito"); - $receptorBarrio = params_get("receptor_barrio"); - $receptorOtrasSenas = params_get("receptor_otras_senas"); - $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - // Detalles de tiquete / Factura - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerado = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravado = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalExonerado = params_get("total_exonerado"); - $totalNoSujeto = params_get("total_no_sujeto"); - $totalVenta = params_get("total_ventas"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - $totalIVADevuelto = params_get("totalIVADevuelto"); - $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); - $otros = json_decode(params_get('otros')); - - // Detalles de la compra - $detalles = json_decode(params_get("detalles")); - $informacionReferencia = json_decode(params_get("informacion_referencia")); - $otrosCargos = json_decode(params_get("otrosCargos")); - $mediosPago = json_decode(params_get("medios_pago")); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($mediosPago) && $mediosPago != "") { - grace_debug(params_get("medios_pago")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); - } - - if (isset($otrosCargos) && $otrosCargos != "") { - if (count($otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); - //Delimita el array a solo 15 elementos - $otrosCargos = array_slice($otrosCargos, 0, 15); - } - } - - if (isset($mediosPago) && $mediosPago != "") { - if (count($mediosPago) > 4) { - error_log("medios_pago: " . count($mediosPago) . " is greater than 4"); - //Delimita el array a solo 4 elementos - $mediosPago = array_slice($mediosPago, 0, 4); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ''; - - if (isset($codigoActividadReceptor) && $codigoActividadReceptor != "") { - $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); - } - - $xmlString .= ' - ' . $codigoActividadReceptor . ''; - } - - $xmlString .= ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); - } - - if ($omitir_receptor != 'true') { - $xmlString .= ' - ' . $receptorNombre . ''; - - if ($receptorTipoIdentif != '' && $receptorNumIdentif != '') { - $xmlString .= ' - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - } - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= '' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' - . $receptorOtrasSenasExtranjero . - ''; - } - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - } - - $xmlString .= ''; - } - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ' - '; - - /* EJEMPLO DE DETALLES - { - "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], - "2":["1","Sp","Honorarios","100000","100000","100000","100000"] - } - */ - - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - - ' . $l . ''; - if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { - $xmlString .= '' . $d->partidaArancelaria . ''; - } - - if (isset($d->codigoCABYS) && $d->codigoCABYS != "") { - $xmlString .= ' - ' . $d->codigoCABYS . ''; - } - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { - $xmlString .= ''; - $lineas = array_slice($d->detalleSurtido, 0, 20); - foreach ($lineas as $linea) { - $xmlString .= ''; - $xmlString .= '' . $linea->codigoCABYSSurtido . ''; - if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { - $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); - foreach ($codigos as $codigo) { - $xmlString .= ''; - $xmlString .= '' . $codigo->tipoSurtido . ''; - $xmlString .= '' . $codigo->codigoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->cantidadSurtido . ''; - $xmlString .= '' . $linea->unidadMedidaSurtido . ''; - if (isset($linea->unidadMedidaComercialSurtido)) { - $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; - } - $xmlString .= '' . $linea->detalleSurtido . ''; - $xmlString .= '' . $linea->precioUnitarioSurtido . ''; - $xmlString .= '' . $linea->montoTotalSurtido . ''; - if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { - $descuentos = array_slice($linea->descuentoSurtido, 0, 5); - foreach ($descuentos as $desc) { - $xmlString .= ''; - $xmlString .= '' . $desc->montoDescuentoSurtido . ''; - $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; - if (isset($desc->descuentoSurtidoOtros)) { - $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; - } - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->subTotalSurtido . ''; - if (isset($linea->ivaCobradoFabricaSurtido)) { - $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; - } - $xmlString .= '' . $linea->baseImponibleSurtido . ''; - if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { - $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); - foreach ($impuestos as $imp) { - $xmlString .= ''; - $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; - if (isset($imp->codigoImpuestoOTROSurtido)) { - $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; - } - if (isset($imp->codigoTarifaIVASurtido)) { - $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; - } - if (isset($imp->tarifaSurtido)) { - $xmlString .= '' . $imp->tarifaSurtido . ''; - } - if (isset($imp->datosImpuestoEspecificoSurtido)) { - $e = $imp->datosImpuestoEspecificoSurtido; - $xmlString .= ''; - if (isset($e->cantidadUnidadMedidaSurtido)) { - $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; - } - if (isset($e->porcentajeSurtido)) { - $xmlString .= '' . $e->porcentajeSurtido . ''; - } - if (isset($e->proporcionSurtido)) { - $xmlString .= '' . $e->proporcionSurtido . ''; - } - if (isset($e->volumenUnidadConsumoSurtido)) { - $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; - } - if (isset($e->impuestoUnidadSurtido)) { - $xmlString .= '' . $e->impuestoUnidadSurtido . ''; - } - $xmlString .= ''; - } - $xmlString .= '' . $imp->montoImpuestoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= ''; - } - $xmlString .= ''; - } - - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . $c['montoDescuento'] . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->IVACobradoFabrica) && $d->IVACobradoFabrica != "") { - $xmlString .= '' . $d->IVACobradoFabrica . ''; - } - - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - ' . $i->codigo . ''; - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - if ( - isset($i->codigo) && - in_array($i->codigo, ["03", "04", "05", "06"]) && - isset($i->datosImpuestoEspecifico) && - is_object($i->datosImpuestoEspecifico) - ) { - $datosImpuestoEsp = $i->datosImpuestoEspecifico; - $xmlString .= ''; - if (isset($datosImpuestoEsp->cantidadUnidadMedida)) { - $xmlString .= '' . $datosImpuestoEsp->cantidadUnidadMedida . ''; - } - if (isset($datosImpuestoEsp->porcentaje)) { - $xmlString .= '' . $datosImpuestoEsp->porcentaje . ''; - } - if (isset($datosImpuestoEsp->proporcion)) { - $xmlString .= '' . $datosImpuestoEsp->proporcion . ''; - } - if (isset($datosImpuestoEsp->volumenUnidadConsumo)) { - $xmlString .= '' . $datosImpuestoEsp->volumenUnidadConsumo . ''; - } - if (isset($datosImpuestoEsp->impuestoUnidad)) { - $xmlString .= '' . $datosImpuestoEsp->impuestoUnidad . ''; - } - $xmlString .= ''; - } - - $xmlString .= '' . $i->monto . ''; - - if (isset($i->montoExportacion) && $i->montoExportacion != "") { - $xmlString .= '' . $i->montoExportacion . ''; - } - - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' - - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; - } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; - } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; - } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; - } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' - '; - } - - $xmlString .= ''; - } - } - if (isset($d->impuestoAsumidoEmisorFabrica) && $d->impuestoAsumidoEmisorFabrica != "") { - $xmlString .= '' . $d->impuestoAsumidoEmisorFabrica . ''; - } - $xmlString .= '' . $d->impuestoNeto . ''; - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerado != '') { - $xmlString .= ' - ' . $totalServExonerado . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if ($totalIVADevuelto != '') { - $xmlString .= ' - ' . $totalIVADevuelto . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} - -function genXMLMr() -{ - - $clave = params_get("clave"); // d{50,50} - // Datos vendedor = emisor - $numeroCedulaEmisor = params_get("numero_cedula_emisor"); // d{12,12} cedula fisica,juridica,NITE,DIMEX - $numeroCedulaEmisor = str_pad($numeroCedulaEmisor, 12, "0", STR_PAD_LEFT); - - // Datos mensaje receptor - $fechaEmisionDoc = params_get("fecha_emision_doc"); // fecha de emision de la confirmacion - $mensaje = params_get("mensaje"); // 1 - Aceptado, 2 - Aceptado Parcialmente, 3 - Rechazado - $detalleMensaje = params_get("detalle_mensaje"); - $montoTotalImpuesto = params_get("monto_total_impuesto"); // d18,5 opcional /obligatorio si comprobante tenga impuesto - $codigoActividad = params_get("codigo_actividad"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $totalFactura = params_get("total_factura"); // d18,5 - $numeroConsecutivoReceptor = params_get("numero_consecutivo_receptor"); // d{20,20} numeracion consecutiva de los mensajes de confirmacion - - // Datos comprador = receptor - $numeroCedulaReceptor = params_get("numero_cedula_receptor"); // d{12,12}cedula fisica, juridica, NITE, DIMEX del comprador - $numeroCedulaReceptor = str_pad($numeroCedulaReceptor, 12, "0", STR_PAD_LEFT); - - // Validate string sizes - $codigoActividad = str_pad($codigoActividad, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividad) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize: " . CODIGOACTIVIDADSIZE . " is not codigoActividad: " . $codigoActividad); - } - - $xmlString = ' - - ' . $clave . ' - ' . $numeroCedulaEmisor . ' - ' . $fechaEmisionDoc . ' - ' . $mensaje . ''; - if (!empty($detalleMensaje)) { - $xmlString .= '' . $detalleMensaje . ''; - } - - if (!empty($montoTotalImpuesto)) { - $xmlString .= '' . $montoTotalImpuesto . ''; - } - $xmlString .= '' . $codigoActividad . ' - ' . $totalFactura . ' - ' . $numeroCedulaReceptor . ' - ' . $numeroConsecutivoReceptor . ''; - - $xmlString .= ''; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); -} - -function genXMLFec() -{ - // Datos contribuyente - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $codigoActividadReceptor = params_get("codigo_actividad_receptor"); - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - // Datos emisor - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorOtrasSenasExtranjero = params_get("emisor_otras_senas_extranjero"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - // Datos receptor - // Deprecated - $omitir_receptor = params_get("omitir_receptor"); - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorProvincia = params_get("receptor_provincia"); - $receptorCanton = params_get("receptor_canton"); - $receptorDistrito = params_get("receptor_distrito"); - $receptorBarrio = params_get("receptor_barrio"); - $receptorOtrasSenas = params_get("receptor_otras_senas"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - // Detalles de tiquete / Factura - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalServExonerado = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravado = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalExonerado = params_get("total_exonerado"); - $totalNoSujeto = params_get("total_no_sujeto"); - $totalVenta = params_get("total_ventas"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - - $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); - $otros = json_decode(params_get('otros')); - - // Detalles de la compra - $detalles = json_decode(params_get("detalles")); - $informacionReferencia = json_decode(params_get("informacion_referencia")); - - $otrosCargos = json_decode(params_get("otrosCargos")); - $mediosPago = json_decode(params_get("medios_pago")); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - grace_debug(params_get("detalles")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($mediosPago) && $mediosPago != "") { - grace_debug(params_get("medios_pago")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - $codigoActividadReceptor = str_pad($codigoActividadReceptor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadReceptor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadReceptor is " . $codigoActividadReceptor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenas) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenas); - } - - if (isset($otrosCargos) && $otrosCargos != "") { - if (count($otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos) . " is greater than 15"); - //Delimita el array a solo 15 elementos - $otrosCargos = array_slice($otrosCargos, 0, 15); - } - } - - if (isset($mediosPago) && $mediosPago != "") { - if (count($mediosPago) > 4) { - error_log("mediosPago: " . count($mediosPago) . " is greater than 4"); - //Delimita el array a solo 4 elementos - $mediosPago = array_slice($mediosPago, 0, 4); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ' - ' . $codigoActividadReceptor . ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; - } - - if ($emisorOtrasSenasExtranjero != '' && strlen($emisorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' . $emisorOtrasSenasExtranjero . ''; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if ($emisorEmail != '' && preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - tools_reply([ - "Status" => 400, - "text" => "El email del emisor ($emisorEmail) no tiene un formato válido.", - ], true); - } - - - $xmlString .= ' - ' . $receptorNombre . ''; - - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if ($receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= '' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - } - - $xmlString .= ''; - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ' - '; - - // cant - unidad medida - detalle - precio unitario - monto total - subtotal - monto total linea - Monto desc -Naturaleza Desc - Impuesto : Codigo / Tarifa / Monto - /* EJEMPLO DE DETALLES - { - "1":["1","Sp","Honorarios","100000","100000","100000","100000","1000","Pronto pago",{"Imp": [{"cod": 122,"tarifa": 1,"monto": 100},{"cod": 133,"tarifa": 1,"monto": 1300}]}], - "2":["1","Sp","Honorarios","100000","100000","100000","100000"] - } - */ - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - - ' . $l . ''; - - $xmlString .= ' - ' . $d->codigoCABYS . ''; - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . $c['montoDescuento'] . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } - - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - - ' . $i->codigo . ''; - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - $xmlString .= '' . $i->monto . ''; - - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' - - ' . $i->exoneracion->tipoDocumento . ''; - if (isset($i->exoneracion->tipoDocumentoOtro) && !empty($i->exoneracion->tipoDocumentoOtro)) { - $xmlString .= '' . $i->exoneracion->tipoDocumentoOtro . ''; - } - $xmlString .= '' . $i->exoneracion->numeroDocumento . ''; - if (isset($i->exoneracion->numeroArticulo) && !empty($i->exoneracion->numeroArticulo)) { - $xmlString .= '' . $i->exoneracion->numeroArticulo . ''; - } - if (isset($i->exoneracion->numeroInciso) && !empty($i->exoneracion->numeroInciso)) { - $xmlString .= '' . $i->exoneracion->numeroInciso . ''; - } - $xmlString .= '' . $i->exoneracion->nombreInstitucion . ''; - if (isset($i->exoneracion->nombreInstitucionOtros) && !empty($i->exoneracion->nombreInstitucionOtros)) { - $xmlString .= '' . $i->exoneracion->nombreInstitucionOtros . ''; - } - $xmlString .= ' - ' . $i->exoneracion->fechaEmision . ' - ' . $i->exoneracion->tarifaExoneracion . ' - ' . $i->exoneracion->montoExoneracion . ' - '; - } - - $xmlString .= ''; - } - } - - $xmlString .= '' . $d->impuestoNeto . ''; - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerado != '') { - $xmlString .= ' - ' . $totalServExonerado . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} - -function genXMLFee() -{ - $clave = params_get("clave"); - $proveedorSistemas = params_get("proveedor_sistemas"); - $codigoActividadEmisor = params_get("codigo_actividad_emisor"); // https://cloud-cube.s3.amazonaws.com/sp5z9nxkd1ra/public/assets/json/actividades_por_codigo.json - $consecutivo = params_get("consecutivo"); - $fechaEmision = params_get("fecha_emision"); - - $emisorNombre = params_get("emisor_nombre"); - $emisorTipoIdentif = params_get("emisor_tipo_identif"); - $emisorNumIdentif = params_get("emisor_num_identif"); - $emisorNombreComercial = params_get("emisor_nombre_comercial"); - $emisorProv = params_get("emisor_provincia"); - $emisorCanton = params_get("emisor_canton"); - $emisorDistrito = params_get("emisor_distrito"); - $emisorBarrio = params_get("emisor_barrio"); - $emisorOtrasSenas = params_get("emisor_otras_senas"); - $emisorCodPaisTel = params_get("emisor_cod_pais_tel"); - $emisorTel = params_get("emisor_tel"); - $emisorEmail = params_get("emisor_email"); - $registroFiscal8707 = params_get("registrofiscal8707"); - - $receptorNombre = params_get("receptor_nombre"); - $receptorTipoIdentif = params_get("receptor_tipo_identif"); - $receptorNumIdentif = params_get("receptor_num_identif"); - $receptorIdentifExtranjero = params_get("receptor_identif_extranjero"); - $receptorNombreComercial = params_get("receptor_nombre_comercial"); - $receptorOtrasSenasExtranjero = params_get("receptor_otras_senas_extranjero"); - $receptorCodPaisTel = params_get("receptor_cod_pais_tel"); - $receptorTel = params_get("receptor_tel"); - $receptorEmail = params_get("receptor_email"); - - $condVenta = params_get("condicion_venta"); - $condVentaOtros = params_get("condicion_venta_otros"); - $plazoCredito = params_get("plazo_credito"); - $detalles = json_decode(params_get("detalles")); - $otrosCargos = json_decode(params_get("otrosCargos")); - $codMoneda = params_get("cod_moneda"); - $tipoCambio = params_get("tipo_cambio"); - - $totalServGravados = params_get("total_serv_gravados"); - $totalServExentos = params_get("total_serv_exentos"); - $totalMercanciasGravadas = params_get("total_merc_gravada"); - $totalMercanciasExentas = params_get("total_merc_exenta"); - $totalGravado = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalVenta = params_get("total_ventas"); - $totalDescuentos = params_get("total_descuentos"); - $totalVentasNeta = params_get("total_ventas_neta"); - $totalImp = params_get("total_impuestos"); - $totalImpAsumidoEmisorFabrica = params_get("total_impuestos_asumidos_fabrica"); - $totalOtrosCargos = params_get("totalOtrosCargos"); - $totalComprobante = params_get("total_comprobante"); - - $informacionReferencia = json_decode(params_get("informacion_referencia")); - $otros = json_decode(params_get('otros')); - // Resumen - $totalDesgloseImpuesto = json_decode(params_get("totalDesgloseImpuesto")); - - grace_debug(params_get("detalles")); - - if (isset($otrosCargos) && $otrosCargos != "") { - grace_debug(params_get("otrosCargos")); - } - - if (isset($totalDesgloseImpuesto) && $totalDesgloseImpuesto != "") { - grace_debug(params_get("totalDesgloseImpuesto")); - } - - // Validate string sizes - $codigoActividadEmisor = str_pad($codigoActividadEmisor, 6, "0", STR_PAD_LEFT); - if (strlen($codigoActividadEmisor) != CODIGOACTIVIDADSIZE) { - error_log("codigoActividadSize is: " . CODIGOACTIVIDADSIZE . " and codigoActividadEmisor is " . $codigoActividadEmisor); - } - - if (strlen($emisorNombre) > EMISORNOMBREMAXSIZE) { - error_log("emisorNombreSize: " . EMISORNOMBREMAXSIZE . " is greater than emisorNombre: " . $emisorNombre); - } - - if (strlen($receptorNombre) > RECEPTORNOMBREMAXSIZE) { - error_log("receptorNombreMaxSize: " . RECEPTORNOMBREMAXSIZE . " is greater than receptorNombre: " . $receptorNombre); - } - - if (strlen($receptorOtrasSenasExtranjero) > RECEPTOROTRASSENASMAXSIZE) { - error_log("RECEPTOROTRASSENASEXTRANJEROMAXSIZE: " . RECEPTOROTRASSENASMAXSIZE . " is greater than receptorOtrasSenas: " . $receptorOtrasSenasExtranjero); - } - - if (isset($otrosCargos) && !empty($otrosCargos)) { - if (count($otrosCargos->otrosCargos) > 15) { - error_log("otrosCargos: " . count($otrosCargos->otrosCargos) . " is greater than 15"); - //Delimita el array a solo 4 elementos - $otrosCargos->otrosCargos = array_slice($otrosCargos->otrosCargos, 0, 15); - } - } - - $xmlString = ' - - ' . $clave . ' - ' . $proveedorSistemas . ' - ' . $codigoActividadEmisor . ' - ' . $consecutivo . ' - ' . $fechaEmision . ' - - ' . $emisorNombre . ' - - ' . $emisorTipoIdentif . ' - ' . $emisorNumIdentif . ' - '; - - if (isset($registroFiscal8707) && $registroFiscal8707 != "") { - $xmlString .= ' - ' . $registroFiscal8707 . ''; - } - - if (isset($emisorNombreComercial) && $emisorNombreComercial != "") { - $xmlString .= ' - ' . $emisorNombreComercial . ''; - } - - if ($emisorProv != '' && $emisorCanton != '' && $emisorDistrito != '' && $emisorOtrasSenas != '') { - $xmlString .= ' - - ' . $emisorProv . ' - ' . $emisorCanton . ' - ' . $emisorDistrito . ''; - if ($emisorBarrio != '') { - $xmlString .= '' . $emisorBarrio . ''; - } - $xmlString .= ' - ' . $emisorOtrasSenas . ' - '; - } - - if ($emisorCodPaisTel != '' && $emisorTel != '' && $emisorTel >= EMISORNUMEROTELMIN && $emisorTel <= EMISORNUMEROTELMAX) { - $xmlString .= ' - - ' . $emisorCodPaisTel . ' - ' . $emisorTel . ' - '; - } - - if (preg_match(EMAIL_REGEX, trim($emisorEmail))) { - $xmlString .= '' . trim($emisorEmail) . ''; - } else { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); - } - - if (isset($receptorNombre) && $receptorNombre != "") { - $xmlString .= ' - ' . $receptorNombre . ''; - } - - if (isset($receptorTipoIdentif) && $receptorTipoIdentif != "" && isset($receptorNumIdentif) && $receptorNumIdentif != "") { - $xmlString .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; - } - - if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { - $xmlString .= ' - ' - . $receptorIdentifExtranjero . - ''; - } - - if (isset($receptorNombreComercial) && $receptorNombreComercial != "") { - $xmlString .= ' - ' . $receptorNombreComercial . ''; - } - - if (isset($receptorProvincia) && $receptorProvincia != '' && $receptorCanton != '' && $receptorDistrito != '' && $receptorOtrasSenas != '') { - $xmlString .= ' - - ' . $receptorProvincia . ' - ' . $receptorCanton . ' - ' . $receptorDistrito . ''; - if ($receptorBarrio != '') { - $xmlString .= '' . $receptorBarrio . ''; - } - $xmlString .= ' - ' . $receptorOtrasSenas . ' - '; - } - - if ($receptorOtrasSenasExtranjero != '' && strlen($receptorOtrasSenasExtranjero) <= RECEPTOROTRASSENASEXTRANJEROMAXSIZE) { - $xmlString .= ' - ' - . $receptorOtrasSenasExtranjero . - ''; - } - - - if ($receptorCodPaisTel != '' && $receptorTel != '') { - $xmlString .= ' - - ' . $receptorCodPaisTel . ' - ' . $receptorTel . ' - '; - } - - if ($receptorEmail != '') { - $xmlString .= '' . $receptorEmail . ''; - $xmlString .= ''; - } - - $xmlString .= ' - ' . $condVenta . ''; - - if (isset($condVentaOtros) && $condVentaOtros != "") { - $xmlString .= ' - ' . $condVentaOtros . ''; - } - - if (isset($plazoCredito) && $plazoCredito != "") { - $xmlString .= ' - ' . $plazoCredito . ''; - } - - $xmlString .= ' - '; - - - $l = 1; - foreach ($detalles as $d) { - $xmlString .= ' - - ' . $l . ''; - - if (isset($d->partidaArancelaria) && $d->partidaArancelaria != "") { - $xmlString .= ' - ' . $d->partidaArancelaria . ''; - } - - $xmlString .= ' - ' . $d->codigoCABYS . ''; - - if (isset($d->codigoComercial) && !empty($d->codigoComercial)) { - // Convertir el objeto $d->codigoComercial en un array - $codigoComercialArray = (array) $d->codigoComercial; - - // Delimitar el array a solo 5 elementos - if (count($codigoComercialArray) > 5) { - error_log("codigoComercial: " . count($codigoComercialArray) . " is greater than 5"); - } - $codigoComercialArray = array_slice($codigoComercialArray, 0, 5); - - // Iterar sobre los elementos del array - foreach ($codigoComercialArray as $codigos) { - $c = (array) $codigos; - // Verificar si el elemento es un array asociativo - if (is_array($c) && isset($c['tipo']) && $c['tipo'] != "" && isset($c['codigo']) && $c['codigo'] != "") { - $xmlString .= ' - - ' . $c['tipo'] . ' - ' . $c['codigo'] . ' - '; - } - } - } - - - $xmlString .= ' - ' . $d->cantidad . ' - ' . $d->unidadMedida . ''; - if (isset($d->tipoTransaccion) && $d->tipoTransaccion != "") { - $xmlString .= ' - ' . $d->tipoTransaccion . ''; - } - if (isset($d->unidadMedidaComercial) && $d->unidadMedidaComercial != "") { - $xmlString .= ' - ' . $d->unidadMedidaComercial . ''; - } - $d->detalle = limpiarTexto($d->detalle, 150); - $xmlString .= ' - ' . $d->detalle . ''; - if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { - $xmlString .= '' . $d->numeroVINoSerie . ''; - } - - if (isset($d->registroMedicamento) && $d->registroMedicamento !== "") { - $xmlString .= '' . htmlspecialchars($d->registroMedicamento) . ''; - } - if (isset($d->formaFarmaceutica) && $d->formaFarmaceutica !== "") { - $xmlString .= '' . htmlspecialchars($d->formaFarmaceutica) . ''; - } - - if (isset($d->detalleSurtido) && is_array($d->detalleSurtido) && count($d->detalleSurtido) > 0) { - $xmlString .= ''; - $lineas = array_slice($d->detalleSurtido, 0, 20); - foreach ($lineas as $linea) { - $xmlString .= ''; - $xmlString .= '' . $linea->codigoCABYSSurtido . ''; - if (isset($linea->codigoComercialSurtido) && is_array($linea->codigoComercialSurtido)) { - $codigos = array_slice($linea->codigoComercialSurtido, 0, 5); - foreach ($codigos as $codigo) { - $xmlString .= ''; - $xmlString .= '' . $codigo->tipoSurtido . ''; - $xmlString .= '' . $codigo->codigoSurtido . ''; - $xmlString .= ''; - } - } - - $xmlString .= '' . $linea->cantidadSurtido . ''; - $xmlString .= '' . $linea->unidadMedidaSurtido . ''; - if (isset($linea->unidadMedidaComercialSurtido)) { - $xmlString .= '' . $linea->unidadMedidaComercialSurtido . ''; - } - - $xmlString .= '' . $linea->detalleSurtido . ''; - $xmlString .= '' . $linea->precioUnitarioSurtido . ''; - $xmlString .= '' . $linea->montoTotalSurtido . ''; - if (isset($linea->descuentoSurtido) && is_array($linea->descuentoSurtido)) { - $descuentos = array_slice($linea->descuentoSurtido, 0, 5); - foreach ($descuentos as $desc) { - $xmlString .= ''; - $xmlString .= '' . $desc->montoDescuentoSurtido . ''; - $xmlString .= '' . $desc->codigoDescuentoSurtido . ''; - if (isset($desc->descuentoSurtidoOtros)) { - $xmlString .= '' . $desc->descuentoSurtidoOtros . ''; - } - $xmlString .= ''; - } - } - $xmlString .= '' . $linea->subTotalSurtido . ''; - if (isset($linea->ivaCobradoFabricaSurtido)) { - $xmlString .= '' . $linea->ivaCobradoFabricaSurtido . ''; - } - $xmlString .= '' . $linea->baseImponibleSurtido . ''; - if (isset($linea->impuestoSurtido) && is_array($linea->impuestoSurtido)) { - $impuestos = array_slice($linea->impuestoSurtido, 0, 1000); - foreach ($impuestos as $imp) { - $xmlString .= ''; - $xmlString .= '' . $imp->codigoImpuestoSurtido . ''; - if (isset($imp->codigoImpuestoOTROSurtido)) { - $xmlString .= '' . $imp->codigoImpuestoOTROSurtido . ''; - } - if (isset($imp->codigoTarifaIVASurtido)) { - $xmlString .= '' . $imp->codigoTarifaIVASurtido . ''; - } - if (isset($imp->tarifaSurtido)) { - $xmlString .= '' . $imp->tarifaSurtido . ''; - } - if (isset($imp->datosImpuestoEspecificoSurtido)) { - $e = $imp->datosImpuestoEspecificoSurtido; - $xmlString .= ''; - if (isset($e->cantidadUnidadMedidaSurtido)) { - $xmlString .= '' . $e->cantidadUnidadMedidaSurtido . ''; - } - if (isset($e->porcentajeSurtido)) { - $xmlString .= '' . $e->porcentajeSurtido . ''; - } - if (isset($e->proporcionSurtido)) { - $xmlString .= '' . $e->proporcionSurtido . ''; - } - if (isset($e->volumenUnidadConsumoSurtido)) { - $xmlString .= '' . $e->volumenUnidadConsumoSurtido . ''; - } - if (isset($e->impuestoUnidadSurtido)) { - $xmlString .= '' . $e->impuestoUnidadSurtido . ''; - } - $xmlString .= ''; - } - $xmlString .= '' . $imp->montoImpuestoSurtido . ''; - $xmlString .= ''; - } - } - $xmlString .= ''; - } - $xmlString .= ''; - } - - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; - - if (isset($d->descuento) && !empty($d->descuento)) { - $descuentoArray = (array) $d->descuento; - - if (count($descuentoArray) > 5) { - error_log("descuento: " . count($descuentoArray) . " is greater than 5"); - } - $descuentoArray = array_slice($descuentoArray, 0, 5); - - foreach ($descuentoArray as $descuentos) { - $c = (array) $descuentos; - if ( - is_array($c) && - isset($c['montoDescuento']) && $c['montoDescuento'] !== "" && - isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" - ) { - $xmlString .= ' - - ' . $c['montoDescuento'] . ' - ' . $c['codigoDescuento'] . ''; - // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo - if ( - isset($c['codigoDescuento']) && $c['codigoDescuento'] === "99" && - isset($c['codigoDescuentoOTRO']) && - strlen($c['codigoDescuentoOTRO']) >= 5 && strlen($c['codigoDescuentoOTRO']) <= 100 - ) { - $xmlString .= '' . htmlspecialchars($c['codigoDescuentoOTRO']) . ''; - } - // NaturalezaDescuento: minOccurs=0, longitud 3-80 - if ( - isset($c['naturalezaDescuento']) && - strlen($c['naturalezaDescuento']) >= 3 && strlen($c['naturalezaDescuento']) <= 80 - ) { - $xmlString .= '' . htmlspecialchars($c['naturalezaDescuento']) . ''; - } - $xmlString .= ' - '; - } - } - } - - $xmlString .= '' . $d->subTotal . ''; - - if (isset($d->impuesto) && $d->impuesto != "") { - foreach ($d->impuesto as $i) { - $xmlString .= ' - '; - if (isset($i->codigo) && $i->codigo != "") { - $xmlString .= '' . $i->codigo . ''; - } - - // Add if required - if ( - isset($i->codigo) && $i->codigo == "99" && - isset($i->codigoImpuestoOtro) && !empty($i->codigoImpuestoOtro) - ) { - $xmlString .= '' . $i->codigoImpuestoOtro . ''; - } - - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; - } - - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; - } - - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; - } - - if (isset($i->monto) && $i->monto != "") { - $xmlString .= '' . $i->monto . ''; - } - - if (isset($i->montoExportacion) && $i->montoExportacion != "") { - $xmlString .= '' . $i->montoExportacion . ''; - } - - $xmlString .= ''; - } - } - - if (isset($d->impuestoNeto) && $d->impuestoNeto != "") { - $xmlString .= '' . $d->impuestoNeto . ''; - } - $xmlString .= '' . $d->montoTotalLinea . ''; - $xmlString .= ''; - $l++; - } - - $xmlString .= ''; - - // JSON DE EJEMPLO - // [ - // { - // "tipoDocumentoOC": "10", - // "tipoDocumentoOTROS": "string", - // "tipoIdentidadTercero": "01", - // "numeroIdentidadTercero": "160029688", - // "nombreTercero": "John Doe", - // "detalle": "Additional charge for service", - // "porcentajeOC": "1452590.23", - // "montoCargo": "1258720.23491" - // }, - // { - // "tipoDocumentoOC": "20", - // "tipoDocumentoOTROS": "other", - // "tipoIdentidadTercero": "02", - // "numeroIdentidadTercero": "123456789", - // "nombreTercero": "Jane Smith", - // "detalle": "Extra fee for expedited processing", - // "porcentajeOC": "10.50", - // "montoCargo": "500.00" - // } - // ] - - //OtrosCargos - if (isset($otrosCargos) && $otrosCargos != "") { - foreach ($otrosCargos as $o) { - $xmlString .= ' - - ' . $o->tipoDocumentoOC . ''; - if (isset($o->tipoDocumentoOTROS) && $o->tipoDocumentoOTROS != "") { - $xmlString .= ' - ' . $o->tipoDocumentoOTROS . ''; - } - if (isset($o->numeroIdentidadTercero) && $o->numeroIdentidadTercero != "" && isset($o->tipoIdentidadTercero) && $o->tipoIdentidadTercero != "") { - $xmlString .= ' - - ' . $o->tipoIdentidadTercero . ' - ' . $o->numeroIdentidadTercero . ' - '; - } - if (isset($o->nombreTercero) && $o->nombreTercero != "") { - $xmlString .= ' - ' . $o->nombreTercero . ''; - } - $o->detalle = limpiarTexto($o->detalle, 150); - $xmlString .= ' - ' . $o->detalle . ''; - if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { - $xmlString .= ' - ' . $o->porcentajeOC . ''; - } - $xmlString .= ' - ' . $o->montoCargo . ''; - $xmlString .= ' - '; - } - } - - // XML Resultante - // - // 10 - // string - // - // 01 - // 160029688 - // - // John Doe - // Additional charge for service - // 1452590.23 - // 1258720.23491 - // - // - // 20 - // other - // - // 02 - // 123456789 - // - // Jane Smith - // Extra fee for expedited processing - // 10.50 - // 500.00 - // - - $xmlString .= ' - '; - - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - - ' . $codMoneda . ' - ' . $tipoCambio . ' - '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalMercanciasGravadas != '') { - $xmlString .= ' - ' . $totalMercanciasGravadas . ''; - } - - if ($totalMercanciasExentas != '') { - $xmlString .= ' - ' . $totalMercanciasExentas . ''; - } - - if ($totalGravado != '') { - $xmlString .= ' - ' . $totalGravado . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - $xmlString .= ' - ' . $totalVenta . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; - } - - $xmlString .= ' - ' . $totalVentasNeta . ''; - - // Add logic for TotalDesgloseImpuesto - if (isset($totalDesgloseImpuesto) && !empty($totalDesgloseImpuesto)) { - foreach ($totalDesgloseImpuesto as $impuesto) { - $xmlString .= ' - '; - if (isset($impuesto->Codigo)) { - $xmlString .= '' . $impuesto->Codigo . ''; - } - if (isset($impuesto->CodigoTarifaIVA)) { - $xmlString .= '' . $impuesto->CodigoTarifaIVA . ''; - } - if (isset($impuesto->TotalMontoImpuesto)) { - $xmlString .= '' . $impuesto->TotalMontoImpuesto . ''; - } - $xmlString .= ''; - } - } - - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; - } - - if ($totalImpAsumidoEmisorFabrica != '') { - $xmlString .= ' - ' . $totalImpAsumidoEmisorFabrica . ''; - } - - if (isset($totalOtrosCargos) && $totalOtrosCargos != "") { - $xmlString .= ' - ' . $totalOtrosCargos . ''; - } - - if (isset($mediosPago) && !empty($mediosPago)) { - foreach ($mediosPago as $o) { - $xmlString .= ' - '; - - // Add TipoMedioPago - if (isset($o->tipoMedioPago) && !empty($o->tipoMedioPago)) { - $xmlString .= '' . $o->tipoMedioPago . ''; - } - - // Add MedioPagoOtros (only if TipoMedioPago is "99") - if (isset($o->tipoMedioPago) && $o->tipoMedioPago === "99" && isset($o->medioPagoOtros) && !empty($o->medioPagoOtros)) { - $xmlString .= '' . htmlspecialchars($o->medioPagoOtros) . ''; - } - - // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; - } - - $xmlString .= ''; - } - } - - - $xmlString .= ' - ' . $totalComprobante . ' - '; - - // JSON de ejemplo - // { - // "informacionReferencia": [ - // { - // "tipoDoc": "01", - // "tipoDocOtro": "Factura", - // "numero": "50620032400020536006000100001010000000017100000017", - // "fechaEmision": "2023-10-01T12:00:00", - // "codigo": "99", - // "codigoOtro": "OTRO1", - // "razon": "Corrección de datos" - // }, - // { - // "tipoDoc": "02", - // "numero": "50620032400020536006000100001010000000017200000018", - // "fechaEmision": "2023-10-02T15:30:00", - // "codigo": "01", - // "razon": "Devolución de producto" - // } - // ] - // } - - if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { - foreach ($informacionReferencia as $ref) { - if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { - if (in_array($ref->tipoDoc, TIPODOCREFVALUES, true)) { - $xmlString .= ''; - $xmlString .= '' . $ref->tipoDoc . ''; - if ($ref->tipoDoc === '99' && isset($ref->tipoDocOtro)) { - $xmlString .= '' . htmlspecialchars($ref->tipoDocOtro) . ''; - } - if (isset($ref->numero)) { - $xmlString .= '' . $ref->numero . ''; - } - $xmlString .= '' . $ref->fechaEmision . ''; - if (isset($ref->codigo)) { - $xmlString .= '' . $ref->codigo . ''; - if ($ref->codigo === '99' && isset($ref->codigoOtro)) { - $xmlString .= '' . htmlspecialchars($ref->codigoOtro) . ''; - } - } - if (isset($ref->razon)) { - $xmlString .= '' . $ref->razon . ''; - } - $xmlString .= ''; - } else { - grace_error("El parámetro tipoDoc no cumple con la estructura establecida. tipoDoc = " . $ref->tipoDoc); - } - } - } - } - - // XML Resultante - // - // 01 - // Factura - // 50620032400020536006000100001010000000017100000017 - // 2023-10-01T12:00:00 - // 99 - // OTRO1 - // Corrección de datos - // - // - // 02 - // 50620032400020536006000100001010000000017200000018 - // 2023-10-02T15:30:00 - // 01 - // Devolución de producto - // - - // ----------------------------------------------------------------------------------------------------- - - // JSON de ejemplo - // { - // "otroTexto": { - // "codigo": "COD1", - // "texto": "Texto opcional 1" - // }, - // "otroContenido": [ - // { - // "codigo": "CONT1", - // "contenidoEstructurado": { - // "ContactoDesarrollador": { - // "Correo": "developer@example.com", - // "Nombre": "Developer Name", - // "Telefono": "+123456789" - // } - // } - // }, - // { - // "codigo": "CONT2", - // "contenidoEstructurado": { - // "SoporteTecnico": { - // "Correo": "support@example.com", - // "Nombre": "Support Team", - // "Telefono": "+987654321" - // } - // } - // } - // ] - //} - - // Start Otros element - $xmlString .= ''; - - // Handle multiple OtroTexto elements - if (isset($otros->otroTexto)) { - if (is_array($otros->otroTexto)) { - foreach ($otros->otroTexto as $otroTexto) { - $codigo = isset($otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otroTexto->codigo) . '"' : ''; - $texto = isset($otroTexto->texto) ? htmlspecialchars($otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } else { - $codigo = isset($otros->otroTexto->codigo) ? ' codigo="' . htmlspecialchars($otros->otroTexto->codigo) . '"' : ''; - $texto = isset($otros->otroTexto->texto) ? htmlspecialchars($otros->otroTexto->texto) : ''; - $xmlString .= '' . $texto . ''; - } - } - - // Handle multiple OtroContenido elements - if (isset($otros->otroContenido) && is_array($otros->otroContenido)) { - foreach ($otros->otroContenido as $otroContenido) { - $codigo = isset($otroContenido->codigo) ? ' codigo="' . htmlspecialchars($otroContenido->codigo) . '"' : ''; - $contenido = ''; - if (isset($otroContenido->contenidoEstructurado) && is_object($otroContenido->contenidoEstructurado)) { - foreach ($otroContenido->contenidoEstructurado as $tag => $data) { - $contenido .= '<' . $tag . '>'; - if (is_object($data)) { - foreach ($data as $k => $v) { - $contenido .= '<' . $k . '>' . htmlspecialchars($v) . ''; - } - } - $contenido .= ''; - } - } - $xmlString .= '' . $contenido . ''; - } - } - - $xmlString .= ''; - - // XML Resultante - // - // Texto opcional 1 - // - // - // developer@example.com - // Developer Name - // +123456789 - // - // - // - // - // support@example.com - // Support Team - // +987654321 - // - // - // - - $xmlString .= ' - '; - - return array( - "clave" => $clave, - "xml" => base64_encode($xmlString), - "validacion" => ValidadorXML::validateXml($xmlString, $consecutivo) - ); -} \ No newline at end of file From 92f7acdb3d6f092045e5efae634e5a8c491a0df2 Mon Sep 17 00:00:00 2001 From: David Obando Date: Sun, 5 Apr 2026 12:19:53 -0400 Subject: [PATCH 30/30] feat: adding latest changes on CRLibre API --- api/contrib/firmarXML/hacienda/firmador.php | 36 ++++++++++++++------- api/contrib/genXML/genXML.php | 11 +------ 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/api/contrib/firmarXML/hacienda/firmador.php b/api/contrib/firmarXML/hacienda/firmador.php index 6b0b3107..daab6146 100644 --- a/api/contrib/firmarXML/hacienda/firmador.php +++ b/api/contrib/firmarXML/hacienda/firmador.php @@ -1,7 +1,7 @@ */ -class Firmador { +class Firmador +{ const FROM_XML_STRING = 1; const FROM_XML_FILE = 2; @@ -46,7 +47,8 @@ class Firmador { const TO_XML_STRING = 4; const TO_XML_FILE = 5; - public function firmarXml($pfx,$pin,$input,$output,$path=null){ + public function firmarXml($pfx, $pin, $input, $output, $path = null) + { // return dirname(__FILE__,2) .'/xmlseclibs/xmlseclibs.php'; // Cargar un nuevo XML para ser firmado $xml = new \DOMDocument(); @@ -59,7 +61,7 @@ public function firmarXml($pfx,$pin,$input,$output,$path=null){ // Intentar parsear el input como archivo xml. Caso contrario se detiene el script try { $xml->loadXML($input); - } catch (\Exception $ex){ + } catch (\Exception $ex) { die($ex->getMessage()); } @@ -72,8 +74,20 @@ public function firmarXml($pfx,$pin,$input,$output,$path=null){ // Establecer política de firma $objSec->setSignPolicy(); + if (empty($pfx) || !is_readable($pfx)) { + $message = 'No se puede acceder al certificado proporcionado.'; + error_log('[Firmador] ' . $message . ' Ruta: ' . $pfx); + throw new \RuntimeException($message); + } + // Cargar la información del certificado desde el archivo *.p12 - $certInfo = $objSec->loadCertInfo($pfx,$pin); + $certInfo = $objSec->loadCertInfo($pfx, $pin); + + if (empty($certInfo) || empty($certInfo["privateKey"])) { + $message = 'Certificado inválido o PIN incorrecto.'; + error_log('[Firmador] ' . $message . ' Ruta: ' . $pfx); + throw new \RuntimeException($message); + } // Usar la canonicalización exclusiva de c14n. $objSec->setCanonicalMethod($objSec::C14N); @@ -91,13 +105,13 @@ public function firmarXml($pfx,$pin,$input,$output,$path=null){ // Firmar utilizando SHA-256 // Referencia del documento - $objSec->addReference($xml,$objSec::SHA256, [ 'http://www.w3.org/2000/09/xmldsig#enveloped-signature' ], [ 'id_ref' => $objSec->reference0Id, 'force_uri' => true ]); + $objSec->addReference($xml, $objSec::SHA256, ['http://www.w3.org/2000/09/xmldsig#enveloped-signature'], ['id_ref' => $objSec->reference0Id, 'force_uri' => true]); // Referencia de nodo de información clave - $objSec->addReference($objSec->getKeyInfoNode(),$objSec::SHA256,null, [ 'id_ref' => $objSec->reference1Id, 'force_uri' => false, 'overwrite' => false ]); + $objSec->addReference($objSec->getKeyInfoNode(), $objSec::SHA256, null, ['id_ref' => $objSec->reference1Id, 'force_uri' => false, 'overwrite' => false]); // Referencia del nodo Xades - $objSec->addReference($objSec->getXadesNode(),$objSec::SHA256,null, [ 'force_uri' => false, 'overwrite' => false, "type" => "http://uri.etsi.org/01903#SignedProperties" ], [ [ 'qualifiedName' => 'xmlns:xades', 'value' => $objSec::XADES ] ]); + $objSec->addReference($objSec->getXadesNode(), $objSec::SHA256, null, ['force_uri' => false, 'overwrite' => false, "type" => "http://uri.etsi.org/01903#SignedProperties"], [['qualifiedName' => 'xmlns:xades', 'value' => $objSec::XADES]]); // Firma el archivo xml $objSec->sign($objKey); @@ -105,13 +119,13 @@ public function firmarXml($pfx,$pin,$input,$output,$path=null){ // Adjuntar la firma al xml $objSec->appendSignature($xml->documentElement); - if ($output == self::TO_BASE64_STRING){ + if ($output == self::TO_BASE64_STRING) { // Devuelve el string del archivo xml firmado en formato Base64 return base64_encode($xml->saveXML()); - } else if ($output == self::TO_XML_STRING){ + } else if ($output == self::TO_XML_STRING) { // Devuelve el archivo xml firmado en formato string Xml return $xml->saveXML(); - } else if ($output == self::TO_XML_FILE){ + } else if ($output == self::TO_XML_FILE) { // Guarda el xml firmado en la ruta especificada y devuelve el resultado if (!is_null($path)) { return $xml->save($path); diff --git a/api/contrib/genXML/genXML.php b/api/contrib/genXML/genXML.php index 427159b3..045420b5 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -1170,7 +1170,7 @@ function genXMLGenerico($tipoDocumento = 'FE') // 4.4 se basa en los cabys para saber que es un servicio o mercaderia, // los codigos que empiezan de 0 a 4 son mercaderias y los que empiezan de 5 a 9 son servicios - $esServicio = (isset($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5', '6', '7', '8', '9'], true)); + $esServicio = (isset($d->codigoCABYS) && !empty($d->codigoCABYS) && in_array($d->codigoCABYS[0], ['5', '6', '7', '8', '9'], true)); $esExento = ($detalleImpuesto->codigoTarifa === "10" || $detalleImpuesto->codigoTarifa === "11"); if ($esExento) { @@ -1520,12 +1520,3 @@ function genXMLGenerico($tipoDocumento = 'FE') "xml" => base64_encode($xmlString) ); } - -/* * ************************************************** */ -/* Funcion de prueba */ -/* * ************************************************** */ - -function test() -{ - return "Esto es un test"; -}