diff --git a/api/contrib/clave/clave.php b/api/contrib/clave/clave.php index c9b34802..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'); @@ -54,9 +53,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)) @@ -85,7 +83,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) { @@ -187,7 +185,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/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 diff --git a/api/contrib/consultar/consultar.php b/api/contrib/consultar/consultar.php index a4f9299c..39e6225d 100644 --- a/api/contrib/consultar/consultar.php +++ b/api/contrib/consultar/consultar.php @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -function consutar() +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,45 +31,179 @@ function consutar() $url = "https://api.comprobanteselectronicos.go.cr/recepcion/v1/recepcion/"; 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( + 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( "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" ), - )); + ); + + 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); - $response = curl_exec($curl); - $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) - { + if ($err) { $arrayResp = array( "Status" => $status, - "to" => $apiTo, + "to" => $url, "text" => $err ); + return $arrayResp; } - else - { - $response = json_decode($response); - return $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 $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, + "text" => $responseObj + ); + } + + 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" => $body + ); +} + + +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 "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( + "Authorization: Bearer " . params_get('token') + ), + ); + + 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) { + $arrayResp = array( + "Status" => $status, + "to" => $url, + "text" => $err + ); + + return tools_reply($arrayResp, true); + } + + 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/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( 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/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/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/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 b197e2c0..045420b5 100644 --- a/api/contrib/genXML/genXML.php +++ b/api/contrib/genXML/genXML.php @@ -19,8 +19,13 @@ /* * ************************************************** */ /* Constantes de validacion */ /* * ************************************************** */ + +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')); + const CODIGOACTIVIDADSIZE = 6; const EMISORNOMBREMAXSIZE = 100; const EMISORNUMEROTELMIN = 8; @@ -30,17 +35,358 @@ 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'); + +/** + * 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); + } +} + + +/** + * 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 + * + * @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(floatval($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 + $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"); @@ -57,11 +403,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"); @@ -83,26 +429,10 @@ 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"); - $totalServExonerados = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = 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"); - $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')); @@ -161,18 +491,65 @@ function genXMLFe() //Delimita el array a solo 4 elementos $mediosPago = array_slice($mediosPago, 0, 4); } - } - - $xmlString = ' - - ' . $clave . ' + } 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 (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); @@ -202,18 +579,17 @@ function genXMLFe() ' . $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) { @@ -225,19 +601,21 @@ 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 .= ' - - ' . $receptorTipoIdentif . ' - ' . $receptorNumIdentif . ' - '; + if (!empty($receptorTipoIdentif) && !empty($receptorNumIdentif)) { + $xmlString .= ' + + ' . $receptorTipoIdentif . ' + ' . $receptorNumIdentif . ' + '; + } if ($receptorIdentifExtranjero != '' && $receptorIdentifExtranjero != '') { $xmlString .= ' @@ -297,6 +675,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 .= ' @@ -432,25 +818,22 @@ function genXMLFe() */ $l = 1; - - foreach ($detalles as $d) { + $calculados = []; - foreach (["codigoCABYS","subTotal","impuestoAsumidoEmisorFabrica","impuestoNeto"] as $requiredField) { - if (!isset($d->{$requiredField}) || $d->{$requiredField} === '') { - tools_reply("Se requiere el campo $requiredField en el detalle #$l", true); - } - } + foreach ($detalles as $d) { $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 - $codigoComercialArray = (array)$d->codigoComercial; + $codigoComercialArray = (array) $d->codigoComercial; // Delimitar el array a solo 5 elementos if (count($codigoComercialArray) > 5) { @@ -460,7 +843,7 @@ function genXMLFe() // 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 .= ' @@ -483,8 +866,10 @@ function genXMLFe() $xmlString .= ' ' . $d->unidadMedidaComercial . ''; } - $xmlString .= ' - ' . $d->detalle . ''; + + $d->detalle = limpiarTexto($d->detalle, 150); + + $xmlString .= '' . $d->detalle . ''; if (isset($d->numeroVINoSerie) && $d->numeroVINoSerie != "") { $xmlString .= '' . $d->numeroVINoSerie . ''; } @@ -579,12 +964,12 @@ function genXMLFe() $xmlString .= ''; } - $xmlString .= ' - ' . $d->precioUnitario . ' - ' . $d->montoTotal . ''; - + $totalDescuentos = 0; + $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"); @@ -592,14 +977,14 @@ function genXMLFe() $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'] !== "" && isset($c['codigoDescuento']) && $c['codigoDescuento'] !== "" ) { - $xmlString .= ' - + $c['montoDescuento'] = round(floatval($c['montoDescuento']), DECIMALES) * $d->cantidad; + $xmlStringDescuento = ' ' . $c['montoDescuento'] . ' ' . $c['codigoDescuento'] . ''; // CodigoDescuentoOTRO: obligatorio si codigoDescuento == "99" y existe el campo @@ -608,64 +993,87 @@ 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 .= ' - '; + $xmlStringDescuento .= ''; + $totalDescuentos += $c['montoDescuento']; } } } - $xmlString .= '' . $d->subTotal . ''; + // 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 . ''; } - if (isset($d->baseImponible) && $d->baseImponible != "") { - $xmlString .= '' . $d->baseImponible . ''; - } + $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 . ''; } - if (isset($i->codigoTarifa) && $i->codigoTarifa != "") { - $xmlString .= '' . $i->codigoTarifa . ''; + // calcular el monto de impuesto si no se proporciona usando el codigo y la tarifa + if ($detalleImpuesto->codigo === "01" && isset($detalleImpuesto->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[$detalleImpuesto->codigoTarifa] * 100; + + $calculados[$l]['monto'] = round(floatval($calculados[$l]['baseImponible']) * $impuestoPorTarifa[$detalleImpuesto->codigoTarifa], DECIMALES); } - if (isset($i->tarifa) && $i->tarifa != "") { - $xmlString .= '' . $i->tarifa . ''; + if (isset($detalleImpuesto->codigoTarifa) && $detalleImpuesto->codigoTarifa != "") { + $xmlString .= '' . $detalleImpuesto->codigoTarifa . ''; } - if (isset($i->factorIVA) && $i->factorIVA != "") { - $xmlString .= '' . $i->factorIVA . ''; + $xmlString .= '' . $calculados[$l]['tarifa'] . ''; + + 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 . ''; @@ -685,45 +1093,154 @@ function genXMLFe() $xmlString .= ''; } - $xmlString .= '' . $i->monto . ''; + if (!isset($detalleImpuesto->monto) || ($detalleImpuesto->monto === 0 && $calculados[$l]['monto'] != 0)) { + $detalleImpuesto->{"monto"} = $calculados[$l]['monto']; + } + + 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 + $xmlStringExoneracion = ''; + if (isset($detalleImpuesto->exoneracion) && $detalleImpuesto->exoneracion != "") { - if (isset($i->exoneracion) && $i->exoneracion != "") { - $xmlString .= ' + // 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); + + $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($detalleImpuesto->codigo) && isset($detalleImpuesto->monto)) { + + foreach (['impuestoNeto'] as $key) { + if (!isset($calculados[$l][$key])) { + $calculados[$l][$key] = 0; + } + } + + 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 { + $calculados[$l]['impuestoNeto'] += round($detalleImpuesto->monto, DECIMALES); + } + + // 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) && !empty($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 { + + // 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; + } + + 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 + ]; + } + + $calculados[$esServicio ? "totalServExonerado" : "totalMercExonerada"] += $montoExonerado; + $calculados['totalExonerado'] += $montoExonerado; + $calculados[$esServicio ? "totalServGravados" : "totalMercanciasGravadas"] += $calculados[$l]['montoTotal']; + $calculados['totalGravado'] += $calculados[$l]['montoTotal']; + } + } } } - $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'] . ''; + + // 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) { @@ -745,6 +1262,7 @@ function genXMLFe() $xmlString .= ' ' . $o->nombreTercero . ''; } + $o->detalle = limpiarTexto($o->detalle, 150); $xmlString .= ' ' . $o->detalle . ''; if (isset($o->porcentajeOC) && $o->porcentajeOC != "") { @@ -758,115 +1276,72 @@ function genXMLFe() } } + foreach ($calculados as $key => $value) { + if (is_numeric($value)) { + $calculados[$key] = round($value, DECIMALES); + } + } + $xmlString .= ' '; - if ($codMoneda != '' && $codMoneda != 'CRC' && $tipoCambio != '' && $tipoCambio != 0) { - $xmlString .= ' - + $xmlString .= ' ' . $codMoneda . ' ' . $tipoCambio . ' '; - } else { - $xmlString .= ' - - CRC - 1 - '; - } - - if ($totalServGravados != '') { - $xmlString .= ' - ' . $totalServGravados . ''; - } - - if ($totalServExentos != '') { - $xmlString .= ' - ' . $totalServExentos . ''; - } - - if ($totalServExonerados != '') { - $xmlString .= ' - ' . $totalServExonerados . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercGravadas != '') { - $xmlString .= ' - ' . $totalMercGravadas . ''; - } - - if ($totalMercExentas != '') { - $xmlString .= ' - ' . $totalMercExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - if ($totalGravados != '') { - $xmlString .= ' - ' . $totalGravados . ''; - } + // 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']; - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } + // 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']; - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } + // Calcular totalVentaNeta + // Se obtiene de la resta de los campos total venta menos total descuento + $calculados['totalVentaNeta'] = $calculados['totalVenta'] - $calculados['totalDescuentos']; - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; + // 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; } - - $xmlString .= ' - ' . $totalVentas . ''; - - if ($totalDescuentos != '') { - $xmlString .= ' - ' . $totalDescuentos . ''; + if ($totalIVADevuelto != '') { + $calculados['totalComprobante'] -= $totalIVADevuelto; } - $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 . ''; + // 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 .= ''; + } } - $xmlString .= ''; + continue; } - } - if ($totalImp != '') { - $xmlString .= ' - ' . $totalImp . ''; + if ($calculados[$campoResumen] != '') { + $xmlString .= '<' . ucfirst($campoResumen) . '>' . $calculados[$campoResumen] . ''; + } } if ($totalImpAsumidoEmisorFabrica != '') { @@ -885,7 +1360,14 @@ function genXMLFe() } 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 .= ' '; @@ -900,18 +1382,26 @@ function genXMLFe() } // Add TotalMedioPago - if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago)) { - $xmlString .= '' . number_format($o->totalMedioPago, 2, '.', '') . ''; + if (isset($o->totalMedioPago) && is_numeric($o->totalMedioPago) && $o->totalMedioPago >= 0) { + $xmlString .= '' . round($o->totalMedioPago, DECIMALES) . ''; } $xmlString .= ''; + $totalMediosPago += floatval($o->totalMedioPago); + } + + $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); } } - $xmlString .= ' - ' . $totalComprobante . ' - '; + $xmlString .= '' . $calculados['totalComprobante'] . ''; + $xmlString .= ' '; + + // Información Referencia if (is_array($informacionReferencia) && count($informacionReferencia) > 0) { foreach ($informacionReferencia as $ref) { if (!empty($ref->tipoDoc) && !empty($ref->fechaEmision)) { @@ -942,36 +1432,6 @@ function genXMLFe() } } - // 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 .= ''; @@ -1006,7 +1466,6 @@ function genXMLFe() $xmlString .= ''; - // XML Resultante // // Texto opcional 1 @@ -1026,4318 +1485,38 @@ function genXMLFe() // // - $xmlString .= ' - '; - $arrayResp = array( + // 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") { + unset($validacion->status); + $validacion->schema = basename($validacion->schema); + 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) ); - - return $arrayResp; } - -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"); - $totalServExonerados = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = 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"); - $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 . ''; - } - $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 . ''; - } - $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 . ''; - } - $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 ($totalServExonerados != '') { - $xmlString .= ' - ' . $totalServExonerados . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercGravadas != '') { - $xmlString .= ' - ' . $totalMercGravadas . ''; - } - - if ($totalMercExentas != '') { - $xmlString .= ' - ' . $totalMercExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravados != '') { - $xmlString .= ' - ' . $totalGravados . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVentas . ''; - - 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 .= ' - '; - - $arrayResp = array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); - - return $arrayResp; -} - -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"); - $totalServExonerados = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = 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"); - $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 . ''; - } - $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 . ''; - } - $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 ($totalServExonerados != '') { - $xmlString .= ' - ' . $totalServExonerados . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercGravadas != '') { - $xmlString .= ' - ' . $totalMercGravadas . ''; - } - - if ($totalMercExentas != '') { - $xmlString .= ' - ' . $totalMercExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravados != '') { - $xmlString .= ' - ' . $totalGravados . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVentas . ''; - - 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 .= ' - '; - - $arrayResp = array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); - - return $arrayResp; -} - -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"); - $totalServExonerados = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = 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"); - $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 . ''; - } - $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 . ''; - } - $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 ($totalServExonerados != '') { - $xmlString .= ' - ' . $totalServExonerados . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercGravadas != '') { - $xmlString .= ' - ' . $totalMercGravadas . ''; - } - - if ($totalMercExentas != '') { - $xmlString .= ' - ' . $totalMercExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravados != '') { - $xmlString .= ' - ' . $totalGravados . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVentas . ''; - - 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 .= ' - '; - $arrayResp = array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); - - return $arrayResp; -} - -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 .= ''; - $arrayResp = array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); - - return $arrayResp; -} - -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 - $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"); - $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"); - $totalServExonerados = params_get("total_serv_exonerados"); - $totalServNoSujeto = params_get("total_serv_no_sujeto"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); - $totalMercExonerada = params_get("total_merc_exonerada"); - $totalMercNoSujeta = params_get("total_merc_no_sujeta"); - $totalGravados = 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"); - $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 { - error_log(sprintf("Invalid email format: '%s' does not meet the regex pattern: %s", $emisorEmail, EMAIL_REGEX)); - } - - - $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 . ''; - } - $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 . ''; - } - $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 ($totalServExonerados != '') { - $xmlString .= ' - ' . $totalServExonerados . ''; - } - - if ($totalServNoSujeto != '') { - $xmlString .= ' - ' . $totalServNoSujeto . ''; - } - - if ($totalMercGravadas != '') { - $xmlString .= ' - ' . $totalMercGravadas . ''; - } - - if ($totalMercExentas != '') { - $xmlString .= ' - ' . $totalMercExentas . ''; - } - - if ($totalMercExonerada != '') { - $xmlString .= ' - ' . $totalMercExonerada . ''; - } - - if ($totalMercNoSujeta != '') { - $xmlString .= ' - ' . $totalMercNoSujeta . ''; - } - - if ($totalGravados != '') { - $xmlString .= ' - ' . $totalGravados . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - if ($totalExonerado != '') { - $xmlString .= ' - ' . $totalExonerado . ''; - } - - if ($totalNoSujeto != '') { - $xmlString .= ' - ' . $totalNoSujeto . ''; - } - - $xmlString .= ' - ' . $totalVentas . ''; - - 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 .= ' - '; - $arrayResp = array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); - - return $arrayResp; -} - -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"); - $totalMercGravadas = params_get("total_merc_gravada"); - $totalMercExentas = params_get("total_merc_exenta"); - $totalGravados = params_get("total_gravados"); - $totalExento = params_get("total_exento"); - $totalVentas = 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 . ''; - } - $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 . ''; - } - $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 ($totalMercGravadas != '') { - $xmlString .= ' - ' . $totalMercGravadas . ''; - } - - if ($totalMercExentas != '') { - $xmlString .= ' - ' . $totalMercExentas . ''; - } - - if ($totalGravados != '') { - $xmlString .= ' - ' . $totalGravados . ''; - } - - if ($totalExento != '') { - $xmlString .= ' - ' . $totalExento . ''; - } - - $xmlString .= ' - ' . $totalVentas . ''; - - 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 .= ' - '; - $arrayResp = array( - "clave" => $clave, - "xml" => base64_encode($xmlString) - ); - - return $arrayResp; -} - - -/* * ************************************************** */ -/* Funcion de prueba */ -/* * ************************************************** */ - -function test() -{ - return "Esto es un test"; -} - -?> diff --git a/api/contrib/genXML/module.php b/api/contrib/genXML/module.php index bcf071aa..57fd1b52 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' @@ -520,3 +520,4 @@ function MODULENAME_access() /**@}*/ /** @}*/ + diff --git a/api/contrib/signXML/Firmadohaciendacr.php b/api/contrib/signXML/Firmadohaciendacr.php index 98a7c04d..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; } @@ -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|CPJ)/', '', $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/params.php b/api/core/params.php index 60210784..20258df1 100644 --- a/api/core/params.php +++ b/api/core/params.php @@ -36,37 +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, $override = false) +function params_set($p, $val = false, $override = true) { global $params; if (is_array($val)) { foreach ($val as $vv => $v) { - _params_set($vv, $v, $override); + if ($override || !array_key_exists($vv, $params)) { + $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 + // 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 $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... diff --git a/api/core/tools.php b/api/core/tools.php index 64c4d821..166be5d7 100644 --- a/api/core/tools.php +++ b/api/core/tools.php @@ -18,80 +18,92 @@ 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'])) { - 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 el codigo de estado HTTP cuando se recibe en el error + if (is_numeric($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 + // y el resto es el mensaje + unset($response['text'][0]); + } + } else { + switch ($response['Status']) { + case 'error': + $httpStatus = 500; + $killMe = true; + break; + case 'ok': + $httpStatus = 200; + break; + default: + $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 @@ -107,10 +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); } 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..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"); 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/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); diff --git a/api/tools/ValidadorXML.php b/api/tools/ValidadorXML.php new file mode 100644 index 00000000..af2dc669 --- /dev/null +++ b/api/tools/ValidadorXML.php @@ -0,0 +1,148 @@ +level) { + case LIBXML_ERR_WARNING: + $r["tipo"] = "warning"; + break; + case LIBXML_ERR_ERROR: + $r["tipo"] = "error"; + break; + case LIBXML_ERR_FATAL: + $r["tipo"] = "fatal_error"; + break; + } + $r["mensaje"] = $error->message; + + return $r; + } + + /** + * Get all libxml errors and clear them + * + * @return array + */ + private static function libxml_display_errors(): array + { + $errors = libxml_get_errors(); + libxml_clear_errors(); + $str_errors = []; + foreach ($errors as $error) { + $str_errors[] = self::libxml_display_error($error); + } + + return $str_errors; + } + + /** + * Valida el XML contra el XSD + * + * @param string $contenido_xml - XML sin firmar (plain text, not base64) + * @param string $consecutivo - Consecutivo del documento + * @param string $version - Versión del schema (4.3 o 4.4) + * @return object Resultado de la validación + */ + public static function validateXml(string $contenido_xml, string $consecutivo, $version = "4.4"): object + { + $tipo_doc = substr($consecutivo, 8, 2); + $baseFolder = conf_get('coreInstall', 'modules', ''); + $baseFolder = preg_replace('/\/api\/$/', '/www/', $baseFolder); + $schemas = [ + "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 ($tipo_doc) al validar.", + ]; + } + + 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 existe el archivo XSD " . $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])) { + $errors = self::libxml_display_errors(); + $r = [ + "status" => "error", + "schema" => $schemas[$tipo_doc], + "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; + } + + 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); + } +} 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 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 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”. - 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”. -