Devengados Incorrectos
- Sueldo mal liquidado
- Horas extras calculadas erradamente
- Bonificaciones con monto incorrecto
- Auxilio de transporte errado
La Nómina Individual de Novedad se utiliza para corregir valores monetarios de un documento de nómina previamente emitido. Este tipo de documento permite ajustar:
Devengados Incorrectos
Deducciones Erróneas
Totales Descuadrados
Ajustes Posteriores
El XML de Novedad es idéntico a la Nómina Individual, con 2 cambios clave:
Nodo Novedad Activo
<Novedad CUNENov="[CUNE_DEL_DOCUMENTO_A_CORREGIR]">true</Novedad>Valores Monetarios Corregidos
<?xml version="1.0" encoding="UTF-8"?><NominaIndividual xmlns="dian:gov:co:facturaelectronica:NominaIndividual" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SchemaLocation="" xsi:schemaLocation="dian:gov:co:facturaelectronica:NominaIndividual NominaIndividualElectronicaXSD.xsd">
<ext:UBLExtensions/>
<!-- CLAVE: Novedad en TRUE con CUNE del documento a corregir --> <Novedad CUNENov="a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2">true</Novedad>
<Periodo FechaIngreso="2024-01-01" FechaRetiro="" FechaLiquidacionInicio="2024-11-01" FechaLiquidacionFin="2024-11-30" TiempoLaborado="330" FechaGen="2024-11-25"/>
<!-- Nuevo consecutivo para la novedad --> <NumeroSecuenciaXML CodigoTrabajador="001" Prefijo="NOV" Consecutivo="00001" Numero="NOV00001"/>
<LugarGeneracionXML Pais="CO" DepartamentoEstado="68" MunicipioCiudad="68001" Idioma="es"/>
<InformacionGeneral Version="V1.0: Documento Soporte de Pago de Nómina Electrónica" Ambiente="2" TipoXML="102" CUNE="" EncripCUNE="" FechaGen="2024-11-25" HoraGen="16:30:00-05:00" PeriodoNomina="5" TipoMoneda="COP" TRM="1"/>
<Notas>Corrección de valores monetarios de la nómina NOM35921. Se ajusta el valor del sueldo trabajado y deducciones.</Notas>
<Empleador RazonSocial="MI EMPRESA SAS" NIT="901143311" DV="8" Pais="CO" DepartamentoEstado="68" MunicipioCiudad="68001" Direccion="Calle 123 # 45-67"/>
<Trabajador TipoTrabajador="01" SubTipoTrabajador="00" AltoRiesgoPension="false" TipoDocumento="13" NumeroDocumento="1234567890" PrimerApellido="PEREZ" SegundoApellido="GOMEZ" PrimerNombre="JUAN" OtrosNombres="CARLOS" LugarTrabajoPais="CO" LugarTrabajoDepartamentoEstado="68" LugarTrabajoMunicipioCiudad="68001" LugarTrabajoDireccion="Carrera 27 # 34-56" SalarioIntegral="false" TipoContrato="1" Sueldo="1400000" CodigoTrabajador="001"/>
<Pago Forma="1" Metodo="10" Banco="BANCOLOMBIA" TipoCuenta="AHORROS" NumeroCuenta="12345678901"/>
<FechasPagos> <FechaPago>2024-11-30</FechaPago> </FechasPagos>
<!-- VALORES CORREGIDOS --> <Devengados> <!-- Sueldo corregido: era 1,300,000 ahora 1,400,000 --> <Basico DiasTrabajados="30" SueldoTrabajado="1400000"/> <Transporte AuxilioTransporte="162000"/> </Devengados>
<Deducciones> <!-- Deducciones recalculadas sobre nuevo total --> <Salud Porcentaje="4.00" Deduccion="62480"/> <FondoPension Porcentaje="4.00" Deduccion="62480"/> </Deducciones>
<Redondeo>0.00</Redondeo>
<!-- TOTALES RECALCULADOS --> <DevengadosTotal>1562000.00</DevengadosTotal> <DeduccionesTotal>124960.00</DeduccionesTotal> <ComprobanteTotal>1437040.00</ComprobanteTotal>
</NominaIndividual>SueldoTrabajadoAuxilioTransporteViaticoManuAlojSViaticoManuAlojNSPago (en HEDs, HENs, etc.)BonificacionS, BonificacionNSAuxilioS, AuxilioNSDeduccion (Salud)Deduccion (FondoPension)DeduccionSP, DeduccionSub (FondoSP)DevengadosTotalDeduccionesTotalComprobanteTotalRedondeoLos siguientes campos NO pueden modificarse con Novedad (usar Nota de Ajuste):
<?phpclass Nomina_Service_NovedadGenerator{ private $xmlGenerator; private $db;
public function __construct() { $this->xmlGenerator = new Nomina_Service_XMLGenerator(); $this->db = Zend_Db_Table::getDefaultAdapter(); }
/** * Genera XML de Novedad para corregir valores monetarios * * @param int $nominaOriginalId ID de la nómina a corregir * @param array $valoresCorregidos Valores a corregir * @return string XML de novedad */ public function generarNovedad($nominaOriginalId, $valoresCorregidos) { // 1. Obtener nómina original $nominaOriginal = $this->obtenerNomina($nominaOriginalId);
if (!$nominaOriginal) { throw new Exception("Nómina original no encontrada"); }
// 2. Verificar que tiene CUNE if (empty($nominaOriginal['cune'])) { throw new Exception("La nómina original no tiene CUNE. Debe estar autorizada."); }
// 3. Crear datos de novedad $datosNovedad = $this->prepararDatosNovedad( $nominaOriginal, $valoresCorregidos );
// 4. Generar XML $xml = $this->xmlGenerator->generarNominaIndividual($datosNovedad);
// 5. Activar nodo Novedad en XML $xml = $this->activarNodoNovedad($xml, $nominaOriginal['cune']);
// 6. Validar XML $validator = new Nomina_Service_XMLValidator(); if (!$validator->validar($xml)) { throw new Exception( 'XML de novedad inválido: ' . implode(', ', $validator->getErrores()) ); }
return $xml; }
private function prepararDatosNovedad($original, $correciones) { $datos = $original; // Copiar datos originales
// Generar nuevo consecutivo para novedad $datos['prefijo'] = 'NOV'; $datos['consecutivo'] = $this->obtenerSiguienteConsecutivo('NOV'); $datos['numero'] = $datos['prefijo'] . $datos['consecutivo'];
// Nueva fecha de generación $datos['fecha_generacion'] = date('Y-m-d'); $datos['hora_generacion'] = date('H:i:s-05:00');
// Aplicar correcciones monetarias if (isset($correciones['sueldo_trabajado'])) { $datos['sueldo_trabajado'] = $correciones['sueldo_trabajado']; }
if (isset($correciones['devengados'])) { $datos['devengados'] = array_merge( $datos['devengados'], $correciones['devengados'] ); }
if (isset($correciones['deducciones'])) { $datos['deducciones'] = array_merge( $datos['deducciones'], $correciones['deducciones'] ); }
// Recalcular totales $datos = $this->recalcularTotales($datos);
// Agregar nota explicativa $datos['notas'] = sprintf( "Corrección de valores monetarios de la nómina %s. " . "Se ajustan los siguientes conceptos: %s", $original['numero'], implode(', ', array_keys($correciones)) );
return $datos; }
private function activarNodoNovedad($xmlString, $cuneOriginal) { $xml = new DOMDocument(); $xml->loadXML($xmlString);
// Buscar nodo Novedad $novedad = $xml->getElementsByTagName('Novedad')->item(0);
if (!$novedad) { throw new Exception('Nodo Novedad no encontrado en XML'); }
// Activar novedad $novedad->setAttribute('CUNENov', $cuneOriginal); $novedad->nodeValue = 'true';
return $xml->saveXML(); }
private function recalcularTotales($datos) { // Calcular DevengadosTotal $devengadosTotal = 0;
// Sueldo básico $devengadosTotal += (float)$datos['sueldo_trabajado'];
// Auxilio de transporte if (isset($datos['devengados']['auxilio_transporte'])) { $devengadosTotal += (float)$datos['devengados']['auxilio_transporte']; }
// Horas extras, bonificaciones, etc. foreach ($datos['devengados'] as $concepto => $valor) { if (is_numeric($valor)) { $devengadosTotal += (float)$valor; } }
// Calcular DeduccionesTotal $deduccionesTotal = 0;
foreach ($datos['deducciones'] as $concepto => $deduccion) { if (isset($deduccion['valor'])) { $deduccionesTotal += (float)$deduccion['valor']; } }
// Calcular ComprobanteTotal $comprobanteTotal = $devengadosTotal - $deduccionesTotal;
// Aplicar redondeo si es necesario $redondeo = 0;
// Actualizar datos $datos['devengados_total'] = round($devengadosTotal, 2); $datos['deducciones_total'] = round($deduccionesTotal, 2); $datos['comprobante_total'] = round($comprobanteTotal, 2); $datos['redondeo'] = round($redondeo, 2);
return $datos; }
private function obtenerSiguienteConsecutivo($prefijo) { $ultimo = $this->db->fetchOne(" SELECT MAX(CAST(consecutivo AS UNSIGNED)) FROM nomina_electronica WHERE prefijo = ? ", [$prefijo]);
return str_pad((int)$ultimo + 1, 5, '0', STR_PAD_LEFT); }}<?phpfunction registrarNovedadEnBD($nominaOriginalId, $xmlNovedad, $transactionID){ $db = Zend_Db_Table::getDefaultAdapter();
// Obtener datos de la nómina original $original = $db->fetchRow( "SELECT * FROM nomina_electronica WHERE id = ?", [$nominaOriginalId] );
// Insertar novedad $novedadId = $db->insert('nomina_electronica', [ 'empleado_id' => $original['empleado_id'], 'periodo_id' => $original['periodo_id'], 'prefijo' => 'NOV', 'consecutivo' => extraerConsecutivo($xmlNovedad), 'numero' => extraerNumero($xmlNovedad), 'tipo_documento' => 'NOVEDAD', 'nomina_original_id' => $nominaOriginalId, 'cune_original' => $original['cune'], 'estado' => 'ENVIADO', 'transaction_id' => $transactionID, 'xml_original' => gzcompress($xmlNovedad), 'fecha_envio' => new Zend_Db_Expr('NOW()'), 'created_at' => new Zend_Db_Expr('NOW()') ]);
// Actualizar nómina original $db->update('nomina_electronica', [ 'tiene_novedad' => 1, 'novedad_id' => $novedadId, 'estado' => 'CORREGIDA_POR_NOVEDAD', 'updated_at' => new Zend_Db_Expr('NOW()') ], "id = $nominaOriginalId");
// Log Zend_Log::info("Novedad registrada", [ 'novedad_id' => $novedadId, 'original_id' => $nominaOriginalId, 'transaction_id' => $transactionID ]);
return $novedadId;}<?php// Controlador o servicio
try { $nominaOriginalId = 12345;
// Definir correcciones $correcciones = [ 'sueldo_trabajado' => 1400000, // Era 1,300,000 'devengados' => [ 'auxilio_transporte' => 162000 // Sin cambios ], 'deducciones' => [ 'salud' => [ 'porcentaje' => 4.00, 'valor' => 62480 // Recalculado sobre nuevo total ], 'pension' => [ 'porcentaje' => 4.00, 'valor' => 62480 ] ] ];
// Generar XML de novedad $generator = new Nomina_Service_NovedadGenerator(); $xmlNovedad = $generator->generarNovedad($nominaOriginalId, $correcciones);
// Codificar y enviar $xmlBase64 = base64_encode($xmlNovedad);
$soapClient = new Dataemunah_Facturatech_SoapClient(); $response = $soapClient->uploadDocument($xmlBase64);
if ($response['success']) { // Registrar en BD $novedadId = registrarNovedadEnBD( $nominaOriginalId, $xmlNovedad, $response['transaccionID'] );
echo "Novedad generada exitosamente. ID: $novedadId\n"; echo "TransactionID: " . $response['transaccionID'] . "\n"; } else { throw new Exception("Error al enviar novedad: " . $response['mensaje']); }
} catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; Zend_Log::err("Error generando novedad", [ 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]);}Antes de Generar
Durante Generación
trueDespués de Enviar