Skip to content

downloadXML

POST SOAP 1.1 Requiere Autorización

El método downloadXML permite descargar el documento XML de nómina electrónica firmado digitalmente por la DIAN. Este XML:

  • Contiene la firma digital oficial
  • Incluye el CUNE (Código Único de Nómina Electrónica)
  • Es el documento válido legalmente
  • Debe almacenarse para auditorías y consultas
  • Se retorna codificado en Base64

ParámetroTipoObligatorioDescripción
usernamestringOKUsuario de acceso al servicio
passwordstringOKContraseña encriptada en SHA256
prefijostringOKPrefijo del documento (ej: “NOM”)
foliostringOKConsecutivo/folio del documento (ej: “35921”)

Request SOAP completo
<?xml version="1.0" encoding="UTF-8"?>
<x:Envelope
xmlns:x="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:dem="urn:https://ws-nomina.facturatech.co/v1/demo/">
<x:Header/>
<x:Body>
<dem:FtechAction.downloadXML x:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<username xsi:type="xsd:string">DATAEM19112025</username>
<password xsi:type="xsd:string">f8c0e8471f126c77bd23f664f3ce251b8f9943f1d95a6ffdda72f8e6b76c9b93</password>
<prefijo xsi:type="xsd:string">NOM</prefijo>
<folio xsi:type="xsd:string">35921</folio>
</dem:FtechAction.downloadXML>
</x:Body>
</x:Envelope>

Response SOAP exitoso
<downloadXMLResponse>
<codigo>200</codigo>
<documentoBase64>PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPE5vbWlu...</documentoBase64>
<mensaje>XML descargado correctamente</mensaje>
<mensajeError></mensajeError>
</downloadXMLResponse>
CampoTipoDescripción
codigointCódigo de estado HTTP
documentoBase64stringXML firmado codificado en Base64
mensajestringDescripción del resultado
mensajeErrorstringDetalle del error (si aplica)

El XML descargado incluye elementos adicionales comparado con el XML original:

<ext:UBLExtensions>
<ext:UBLExtension>
<ext:ExtensionContent>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="..."/>
<ds:SignatureMethod Algorithm="..."/>
<ds:Reference URI="">
<ds:DigestMethod Algorithm="..."/>
<ds:DigestValue>...</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
<ds:KeyInfo>...</ds:KeyInfo>
</ds:Signature>
</ext:ExtensionContent>
</ext:UBLExtension>
</ext:UBLExtensions>
<InformacionGeneral
Version="V1.0: Documento Soporte de Pago de Nómina Electrónica"
Ambiente="2"
TipoXML="102"
FechaGen="2024-11-20"
HoraGen="15:13:46-05:00"
CUNE="a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6"
EncripCUNE="SHA384"
PeriodoNomina="5"
TipoMoneda="COP"
TRM="1"/>
<ProveedorXML
RazonSocial="FACTURATECH SAS"
NIT="900123456"
DV="7"
SoftwareID="software-id-facturatech"
SoftwareSC="security-code-123"/>

Base de Datos

Ventajas:

  • Búsqueda rápida
  • Respaldos automáticos
  • Control de versiones

Consideraciones:

  • Comprimir con gzip
  • Usar MEDIUMBLOB/LONGBLOB
  • Indexar por número, CUNE, fecha

Sistema de Archivos

Ventajas:

  • Fácil descarga directa
  • No ocupa espacio BD
  • Simple de respaldar

Consideraciones:

  • Estructura de carpetas por año/mes
  • Permisos restrictivos
  • Sincronización en cluster

Object Storage

Ventajas:

  • Escalable infinitamente
  • Alta disponibilidad
  • Versionamiento nativo

Consideraciones:

  • Costo por almacenamiento
  • Latencia de red
  • Configurar CDN si es público

Enfoque Híbrido

Recomendado:

  • BD: Metadata + hash
  • Archivos: XML comprimido
  • Cloud: Backup mensual

Beneficios:

  • Mejor performance
  • Redundancia
  • Optimización de costos

Implementación de Almacenamiento Híbrido

Section titled “Implementación de Almacenamiento Híbrido”
StorageManager.php
<?php
class Nomina_Storage_XMLManager
{
private $baseDir = '/var/www/dataemunah/storage/nominas';
private $db;
public function __construct()
{
$this->db = Zend_Db_Table::getDefaultAdapter();
}
/**
* Almacena XML firmado en sistema de archivos y metadata en BD
*/
public function almacenarXMLFirmado($numero, $xmlContent, $cune)
{
// 1. Generar ruta por fecha
$fecha = date('Y-m');
$dir = "{$this->baseDir}/{$fecha}";
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
// 2. Comprimir XML
$xmlComprimido = gzcompress($xmlContent, 9);
// 3. Guardar archivo
$filename = "{$numero}_firmado.xml.gz";
$filepath = "{$dir}/{$filename}";
if (file_put_contents($filepath, $xmlComprimido) === false) {
throw new Exception("Error al guardar archivo XML");
}
// 4. Calcular hash para verificación de integridad
$hash = hash('sha256', $xmlContent);
// 5. Actualizar metadata en BD
$this->db->update('nomina_electronica', [
'xml_firmado_path' => $filepath,
'xml_firmado_hash' => $hash,
'xml_firmado_tamano' => strlen($xmlContent),
'xml_firmado_comprimido_tamano' => strlen($xmlComprimido),
'cune' => $cune,
'fecha_descarga_xml' => new Zend_Db_Expr('NOW()'),
'estado' => 'XML_DESCARGADO'
], "numero = " . $this->db->quote($numero));
// 6. Log
Zend_Log::info("XML firmado almacenado", [
'numero' => $numero,
'path' => $filepath,
'tamano_original' => strlen($xmlContent),
'tamano_comprimido' => strlen($xmlComprimido),
'ratio_compresion' => round((1 - strlen($xmlComprimido) / strlen($xmlContent)) * 100, 2) . '%'
]);
return [
'path' => $filepath,
'hash' => $hash,
'tamano' => strlen($xmlContent)
];
}
/**
* Recupera XML firmado desde storage
*/
public function recuperarXMLFirmado($numero)
{
// 1. Obtener path desde BD
$row = $this->db->fetchRow(
"SELECT xml_firmado_path, xml_firmado_hash
FROM nomina_electronica
WHERE numero = ?",
[$numero]
);
if (!$row) {
throw new Exception("Nómina no encontrada: $numero");
}
$filepath = $row['xml_firmado_path'];
if (!file_exists($filepath)) {
throw new Exception("Archivo XML no encontrado: $filepath");
}
// 2. Leer y descomprimir
$xmlComprimido = file_get_contents($filepath);
$xmlContent = gzuncompress($xmlComprimido);
// 3. Verificar integridad
$hashCalculado = hash('sha256', $xmlContent);
if ($hashCalculado !== $row['xml_firmado_hash']) {
Zend_Log::error("Hash de XML no coincide para nómina $numero");
throw new Exception("Integridad del XML comprometida");
}
return $xmlContent;
}
/**
* Genera archivo XML para descarga web
*/
public function generarDescarga($numero, $outputFilename = null)
{
$xmlContent = $this->recuperarXMLFirmado($numero);
if ($outputFilename === null) {
$outputFilename = "{$numero}_nomina_electronica.xml";
}
header('Content-Type: application/xml; charset=utf-8');
header('Content-Disposition: attachment; filename="' . $outputFilename . '"');
header('Content-Length: ' . strlen($xmlContent));
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
echo $xmlContent;
exit;
}
}

Verificar permisos antes de descargar
<?php
function verificarPermisoDescarga($usuarioId, $nominaId)
{
$db = Zend_Db_Table::getDefaultAdapter();
// Verificar que el usuario tenga acceso a esta nómina
$acceso = $db->fetchOne("
SELECT COUNT(*)
FROM nomina_electronica ne
INNER JOIN empleados e ON ne.empleado_id = e.id
INNER JOIN usuarios_empresas ue ON e.empresa_id = ue.empresa_id
WHERE ne.id = ?
AND ue.usuario_id = ?
AND ue.puede_ver_nominas = 1
", [$nominaId, $usuarioId]);
if ($acceso == 0) {
throw new Exception("Usuario no autorizado para ver esta nómina");
}
// Log de acceso
$db->insert('nomina_accesos_log', [
'nomina_id' => $nominaId,
'usuario_id' => $usuarioId,
'accion' => 'DESCARGA_XML',
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'fecha' => new Zend_Db_Expr('NOW()')
]);
return true;
}

Script de validación completo
<?php
class Nomina_Validator_XMLFirmado
{
public function validar($xmlContent)
{
$errores = [];
// 1. Validar que sea XML válido
libxml_use_internal_errors(true);
$xml = simplexml_load_string($xmlContent);
if ($xml === false) {
foreach (libxml_get_errors() as $error) {
$errores[] = "XML mal formado: " . $error->message;
}
return ['valido' => false, 'errores' => $errores];
}
// 2. Verificar namespace correcto
$namespaces = $xml->getNamespaces(true);
if (!isset($namespaces[''])) {
$errores[] = "Namespace DIAN no encontrado";
}
// 3. Verificar firma digital
$ext = $xml->children('ext', true);
if (!isset($ext->UBLExtensions)) {
$errores[] = "Firma digital no encontrada";
}
// 4. Verificar CUNE
$infoGeneral = $xml->InformacionGeneral;
$cune = (string)$infoGeneral['CUNE'];
if (empty($cune)) {
$errores[] = "CUNE no encontrado";
} elseif (!preg_match('/^[a-f0-9]{64}$/i', $cune)) {
$errores[] = "Formato de CUNE inválido";
}
// 5. Verificar proveedor
if (!isset($xml->ProveedorXML)) {
$errores[] = "Información del proveedor no encontrada";
}
// 6. Verificar coherencia de datos
$devengadosTotal = (float)$xml->DevengadosTotal;
$deduccionesTotal = (float)$xml->DeduccionesTotal;
$comprobanteTotal = (float)$xml->ComprobanteTotal;
$totalCalculado = $devengadosTotal - $deduccionesTotal;
$diferencia = abs($totalCalculado - $comprobanteTotal);
if ($diferencia > 2.0) { // Tolerancia de ±2.00
$errores[] = "Totales no cuadran (diferencia: $diferencia)";
}
return [
'valido' => empty($errores),
'errores' => $errores,
'cune' => $cune ?? null,
'numero' => (string)$xml->NumeroSecuenciaXML['Numero'] ?? null
];
}
}

  • XML descargado y decodificado correctamente
  • Archivo guardado en storage
  • Metadata actualizada en BD
  • Hash SHA256 calculado y almacenado
  • Validación de integridad ejecutada
  • CUNE extraído y registrado
  • Log de descarga registrado
  • Auditoría de acceso completada
  • Backup programado
  • Estado de nómina actualizado a “XML_DESCARGADO”

Después de descargar el XML firmado:

  1. downloadPDF - Obtener representación gráfica
  2. downloadCUNE - Confirmar CUNE
  3. Enviar documentos al empleado por email
  4. Programar backup de archivos