Aprende Java Aprende Php Aprende C++ Aprende HTML 5 Aprende JavaScript Aprende JSON Aprende MySQL Aprende SQLServer Aprende Visual Basic 6 Aprende PostgreSQL Aprende SQLite Aprende Redis Aprende Kotlin Aprende XML Aprende Linux VSC Aprende Wordpress Aprende Laravel Aprende VueJS Aprende JQuery Aprende Bootstrap Aprende Netbeans Aprende Android
Sigueme en Facebook Sigueme en Twitter Sigueme en Instagram Sigueme en Youtube Sigueme en TikTok Sigueme en Whatsapp
Home / Proyectos / Facturación electrónica: El Código de Control

Facturación electrónica: El Código de Control

Por jc mouse jueves, abril 7, 2016

¿Que es la facturación electrónica?

Una factura es un documento que sirve para describir el costo de los servicios y desglosar los impuestos correspondientes a pagar, por tanto una factura electrónica es la versión digital de las facturas tradicionales, legalmente válido como medio de respaldo de las operaciones comerciales entre contribuyentes se expide y recibe en formato electrónico.

Facturación electrónica en Bolivia

facturacion virtual

Cada país cuenta con su propia normativa en cuanto a la implementación de facturación virtual, para Bolivia esta se encuentra en el documento alojado en la web de Impuestos nacionales (SIN) «Sistema de Facturación Virtual«. Así mismo, este tipo de facturación debe contar con medidas de seguridad, por ejemplo un Código de Control:

«En el marco del Nuevo Sistema de Facturación implementado por la Administración Tributaria, se tiene prevista la incorporación de nuevos elementos de seguridad en las facturas emitidas por sistemas de facturación computarizada. En este sentido, toda factura emitida por este medio, deberá incorporar un Código de Control generado a partir de información de la misma.» [SIN]

¿Qué es el Código de Control?

Es un dato alfanumérico generado por un sistema de facturación computarizada a tiempo de emitir una factura. Constituye una representación única de una factura, que será empleada por el SIN (Servicio de Impuestos Nacionales) para que junto a otra información permitan determinar la validez o no de la misma.
Este código se genera en base a información de dosificación de la factura, información de la transacción comercial, y un dato alfanumérico denominado Llave de Dosificación, que el contribuyente recibirá por Internet cada vez que solicite dosificaciones de facturas para su sistema de facturación computarizada.

Generación del Código de Control

Algoritmos utilizados

Para generar un Código de Control, se hace uso de los siguientes algoritmos informáticos:

  • Alleged RC4. Un algoritmo de criptografía simétrica, basado en cifrado de flujo (stream cipher), muy utilizado por su rendimiento y simplicidad.
  • Verhoeff .Algoritmo de dígito verificador que trabaja con cadenas de dígitos decimales de cualquier tamaño. Además de detectar una amplia gama de errores en datos numéricos, este algoritmo también detecta casos de
    transposición de dígitos adyacentes.
  • Base 64. Algoritmo que convierte cifras en base 10 a base 64, utilizando divisiones sucesivas además de un
    diccionario de 64 caracteres.

Para generar un Código de Control, se requiere de la siguiente información

Datos de dosificación:

  • Número de autorización: Dato numérico de máximo 15 dígitos. (Ej.: 29040011007)
  • Número de factura: Dato numérico de máximo 12 dígitos. (Ej.:1503)

Datos de la transacción comercial:

  • CI o NIT del cliente: Dato numérico de máximo 12 dígitos. (Ej.: 4189179011)
  • Fecha de la transacción: Dato numérico de 8 dígitos, en el formato AAAAMMDD. (Ej.: 20070702)
  • Monto de la transacción: Importe de la factura sujeto a débito fiscal. Solo para efectos del Código de
    Control, este monto deberá expresarse sin centavos, redondeado al inmediato superior a partir de los
    50 centavos (Según Art. 11 de la RA Nº 05-0048-99). En el caso de Notas de Crédito – Débito, el monto a
    utilizarse será el de Monto Efectivo del Crédito – Débito.

Llave de Dosificación

  • Llave asignada por el SIN a la dosificación solicitada por el contribuyente. Constituye la llave privada
    utilizada por el algoritmo de criptografía. Dato alfanumérico de hasta 256 caracteres generado a partir
    del siguiente diccionario:
A, B, C, D, E, F, G, H, I, J, K, L, M, N, P, Q, R, S, T, U, V, W, X, Y, Z,
a, b, c, d, e, f, g, h, i, j, k, m, n, p, q, r, s, t, u, v, w, x, y, z, 2,
3, 4, 5, 6, 7, 8, 9, =, #, (, ), *, +, -, _, \, @, [, ], {, }, %, $

El Código de Control generado a partir de los algoritmos mencionados, será un dato alfanumérico de hasta 10 caracteres, representado en grupos de 2 separados por el caracter «-«. Ej.: 6A-DC-53-05-14

Proyecto «Código de Control»

  • Lenguaje: Java 8
  • IDE: Netbeans

impuestos bolivia

SIN Bolivia pone a disposición de aquel que este interesado en el tema de documentación bastante detallada el cual solo debemos seguir paso a paso.  «DOCUMENTO DE ESPECIFICACIONES TÉCNICAS DEL CÓDIGO DE CONTROL – VERSIÓN 7.0«.

Algoritmo de Verhoeff (Documentación)

/**
 * @see <a href="http://en.wikipedia.org/wiki/Verhoeff_algorithm">More Info</a>
 * @see <a href="http://en.wikipedia.org/wiki/Dihedral_group">Dihedral Group</a>
 * @see <a href="http://mathworld.wolfram.com/DihedralGroupD5.html">Dihedral Group Order 10</a>
 * @author Colm Rice
 */
public class Verhoeff {
    
    // The multiplication table
    static int[][] d  = new int[][]
    {
        {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 
        {1, 2, 3, 4, 0, 6, 7, 8, 9, 5}, 
        {2, 3, 4, 0, 1, 7, 8, 9, 5, 6}, 
        {3, 4, 0, 1, 2, 8, 9, 5, 6, 7}, 
        {4, 0, 1, 2, 3, 9, 5, 6, 7, 8}, 
        {5, 9, 8, 7, 6, 0, 4, 3, 2, 1}, 
        {6, 5, 9, 8, 7, 1, 0, 4, 3, 2}, 
        {7, 6, 5, 9, 8, 2, 1, 0, 4, 3}, 
        {8, 7, 6, 5, 9, 3, 2, 1, 0, 4}, 
        {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    };
	
    // The permutation table
    static int[][] p = new int[][]
    {
        {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 
        {1, 5, 7, 6, 2, 8, 3, 0, 9, 4}, 
        {5, 8, 0, 3, 7, 9, 6, 1, 4, 2}, 
        {8, 9, 1, 6, 0, 4, 3, 5, 2, 7}, 
        {9, 4, 5, 3, 1, 2, 6, 8, 7, 0}, 
        {4, 2, 8, 6, 5, 7, 3, 9, 0, 1}, 
        {2, 7, 9, 3, 8, 0, 6, 4, 1, 5}, 
        {7, 0, 4, 6, 9, 1, 3, 2, 5, 8}
    };
	
    // The inverse table
    static int[] inv = {0, 4, 3, 2, 1, 5, 6, 7, 8, 9};
    
    /* 
     * For a given number generates a Verhoeff digit    
     */
    public static String generateVerhoeff(String num){
        int c = 0;
	int[] myArray = stringToReversedIntArray(num);
	for(int i = 0; i < myArray.length; i++){
            c = d[c][p[((i + 1) % 8)] [myArray[i]]];
        }		
        return Integer.toString(inv[c]);
    }
	
    /*
    * Validates that an entered number is Verhoeff compliant.
    * NB: Make sure the check digit is the last one.
    */
    public static boolean validateVerhoeff(String num){		
        int c = 0;
        int[] myArray = stringToReversedIntArray(num);        
        for (int i = 0; i < myArray.length; i++){
            c = d[c][p[(i % 8)][myArray[i]]];
        }		
        return (c == 0);
    }
	
    /*
     * Converts a string to a reversed integer array.
     */
    private static int[] stringToReversedIntArray(String num){		
        int[] myArray = new int[num.length()];		
        for(int i = 0; i < num.length(); i++){
            myArray[i] = Integer.parseInt(num.substring(i, i + 1));					
        }		
        myArray = reverse(myArray);		
        return myArray;	
    }
	
    /*
    * Reverses an int array
    */
    private static int[] reverse(int[] myArray){
        int[] reversed = new int[myArray.length];		
        for(int i = 0; i < myArray.length ; i++){
            reversed[i] = myArray[myArray.length - (i + 1)];			
        }
        return reversed;
    }	
}

Algoritmo Base 64 (Documentación)

/**
 * @see https://www.jc-mouse.net/
 * @author mouse
 */
public class Base64SIN {
    
    public static String convert(int value){
        String[] dictionary = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 
                                "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
                                "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", 
                                "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d",
                                "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", 
                                "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", 
                                "y", "z", "+", "/" };
        int quotient = 1; 
        int remainder;         
        String word = "";
        while (quotient > 0)
        {
                quotient = value / 64;
                remainder = value % 64;
                word = dictionary[remainder] + word;
                value = quotient;
        }
        return word;
    }
}

Algoritmo Alleged RC4 (Documentacion)

/**
 * @see https://www.jc-mouse.net/
 * @author Mouse
 */
public class AllegedRC4 {
    
/**
     * Retorna mensaje encriptado
     * @param message mensaje a encriptar
     * @param key llave 
     * @return String mensaje encriptado
     */
    public static String encryptMessageRC4(String message, String key){
        int state[] = new int[256];
        int x=0;
        int y=0;
        int index1=0;
        int index2=0;        
        int nmen;        
        String messageEncryption="";
        
        for(int i=0;i<=255;i++){
            state[i]=i;
        }
        
        for(int i=0;i<=255;i++){
            //Index2 = ( ObtieneASCII(key[Index1]) + State[I] + Index2 ) MODULO 256
            index2 =  ( ( (int) key.toCharArray()[index1] ) +  state[i] + index2) % 256;
            //IntercambiaValor( State[I], State[Index2] )
            int aux = state[i];
            state[i] = state[index2];
            state[index2] = aux;
            //Index1 = (Index1 + 1) MODULO LargoCadena(Key)
            index1 = (index1 + 1 ) % key.length();
        }
        
        //PARA I = 0 HASTA LargoCadena(Mensaje)-1 HACER
        for(int i=0; i<message.length();i++ ){
            //X = (X + 1) MODULO 256
            x = (x + 1) % 256;
            //Y = (State[X] + Y) MODULO 256
            y = (state[x] + y) % 256;
            //IntercambiaValor( State[X] , State[Y] )
            int aux = state[x];
            state[x] = state[y];
            state[y] = aux; 
            //NMen = ObtieneASCII(Mensaje[I]) XOR State[(State[X] + State[Y]) MODULO 256]
            nmen = ( (int) message.toCharArray()[i]) ^ state[(state[x]+state[y]) % 256];
            //MensajeCifrado = MensajeCifrado + "-" + RellenaCero(ConvierteAHexadecimal(NMen))            
            String nmenHex = Integer.toHexString(nmen).toUpperCase();
            messageEncryption = messageEncryption + "-" + ((nmenHex.length()==1)?("0"+nmenHex):nmenHex);            
        }
        //RETORNAR ObtieneSubCadena(MensajeCifrado, 1, LargoCadena(MensajeCifrado) - 1);
        return messageEncryption.substring(1,messageEncryption.length());
    }
        
    /**
     * Retorna mensaje encriptado sin guion (-)
     * @param message mensaje a encriptar
     * @param key llave 
     * @return String mensaje encriptado
     */
    public static String encryptMessageRC4Unscripted(String message,String key){        
        String resul = encryptMessageRC4(message, key);
        resul=resul.replace("-","");
        return resul;
    }
}

Clase para generar código de control: ControlCode.java (Documentación)

Una vez que tenemos listos las clases de los diferentes algoritmos, guiándonos con la documentacion de SIN desarrollamos la siguiente clase:

import java.math.BigDecimal;
import java.util.ArrayList;
/**
 * @see https://www.jc-mouse.net/ 
 * @author Mouse
 */
public class ControlCode {
    
    //datos para generar codigo de control
    private String authorizationNumber; //Número de Autorización 
    private String invoiceNumber; //Número de Factura
    private String NITCI;
    private String dateOfTransaction; //Fecha de la Transacción
    private String transactionAmount; //Monto de la Transacción
    private String dosageKey;//Llave de Dosificación
    //otras variables :)
    private String fiveDigitsVerhoeff;
    private String stringDKey;
    private int sumProduct;
    private String base64SIN;
    
    /**
     * Constructor de clase
     */
    public ControlCode(){}
    
    /**
     * Genera el codigo de control
     * @param aNumber Numero de autorizacion
     * @param iNumber Numero de factura
     * @param nitci NIT o CI 
     * @param dTransaction fecha de transaccion de la forma:
     *                       2007/07/02 a 20070702
     * @param tAmount Monto de la transacción 
     * @param dKey Llave de dosificacion
     * @return String Codigo de control
     */
    public String generate(String aNumber, String iNumber, String nitci, 
                           String dTransaction, String tAmount, String dKey){
        this.authorizationNumber = aNumber;
        this.invoiceNumber = iNumber;
        this.NITCI =nitci;
        this.dateOfTransaction = dTransaction;
        this.transactionAmount = roundUp(tAmount);
        this.dosageKey = dKey;
        
        /* ========== PASO 1 ============= */
        invoiceNumber = addVerhoeffDigit(invoiceNumber,2);
        NITCI = addVerhoeffDigit(NITCI,2);
        dateOfTransaction = addVerhoeffDigit(dateOfTransaction,2);
        this.transactionAmount = addVerhoeffDigit(transactionAmount,2);
        //se suman todos los valores obtenidos
        Long sumOfVariables = Long.valueOf(invoiceNumber)
                              + Long.valueOf(NITCI)
                              + Long.valueOf(dateOfTransaction)
                              + Long.valueOf(transactionAmount);
        //A la suma total se añade 5 digitos Verhoeff
        String sumOfVariables5Verhoeff = addVerhoeffDigit(String.valueOf(sumOfVariables),5);        
                
        /* ========== PASO 2 ============= */
        fiveDigitsVerhoeff = sumOfVariables5Verhoeff.substring(sumOfVariables5Verhoeff.length()-5);
        
        String[] ary = fiveDigitsVerhoeff.split("");//java 8
        int[] numbers = new int[ary.length];
        for(int i=0;i<ary.length;i++){
             numbers[i] = Integer.parseInt(ary[i]) + 1;
        }
                
        String string1 = dosageKey.substring(0, numbers[0] );
        String string2 = dosageKey.substring(numbers[0], numbers[0]+numbers[1] );
        String string3 = dosageKey.substring(numbers[0]+numbers[1], numbers[0]+numbers[1]+numbers[2] );
        String string4 = dosageKey.substring(numbers[0]+numbers[1]+numbers[2], numbers[0]+numbers[1]+numbers[2]+numbers[3] );
        String string5 = dosageKey.substring(numbers[0]+numbers[1]+numbers[2]+numbers[3], numbers[0]+numbers[1]+numbers[2]+numbers[3]+numbers[4] );
        
        String authorizationNumberDKey = authorizationNumber + string1;
        String invoiceNumberdKey = invoiceNumber + string2;
        String NITCIDKey = NITCI + string3;
        String dateOfTransactionDKey = dateOfTransaction + string4;        
        String transactionAmountDKey = transactionAmount + string5;
        
        /* ========== PASO 3 ============= */        
        //se concatena cadenas de paso 2
        stringDKey = authorizationNumberDKey + invoiceNumberdKey + NITCIDKey + dateOfTransactionDKey + transactionAmountDKey; 
        //Llave para cifrado + 5 digitos Verhoeff generado en paso 2
        String keyForEncryption = dosageKey + fiveDigitsVerhoeff;      
        //se aplica AllegedRC4
        String allegedRC4String = AllegedRC4.encryptMessageRC4Unscripted(stringDKey, keyForEncryption);
        
        /* ========== PASO 4 ============= */
        //cadena encriptada en paso 3 se convierte a un Array 
        ArrayList<Character> chars = new ArrayList();
        for (char c : allegedRC4String.toCharArray()) {
            chars.add(c);
        }
        //se suman valores ascii
        int totalAmount=0;
        int sp1=0;
        int sp2=0;
        int sp3=0;
        int sp4=0;
        int sp5=0;
        
        int tmp=1;
        for(char c:chars){
            totalAmount += (int)c;
            switch(tmp){
                case 1: sp1 += (int)c; break;
                case 2: sp2 += (int)c; break;
                case 3: sp3 += (int)c; break;
                case 4: sp4 += (int)c; break;
                case 5: sp5 += (int)c; break;
            }            
            tmp = (tmp<5)?tmp+1:1;
        }
                
        /* ========== PASO 5 ============= */    
        //suma total * sumas parciales dividido entre resultados obtenidos 
        //entre el dígito Verhoeff correspondiente más 1 (paso 2)
        int tmp1 = totalAmount*sp1/numbers[0];
        int tmp2 = totalAmount*sp2/numbers[1];
        int tmp3 = totalAmount*sp3/numbers[2];
        int tmp4 = totalAmount*sp4/numbers[3];
        int tmp5 = totalAmount*sp5/numbers[4];
        //se suman todos los resultados
        sumProduct = tmp1 + tmp2 + tmp3 + tmp4 +tmp5;        
        //se obtiene base64
        base64SIN= Base64SIN.convert(sumProduct);
        
        /* ========== PASO 6 ============= */        
        //Aplicar el AllegedRC4 a la anterior expresión obtenida
        return AllegedRC4.encryptMessageRC4(base64SIN, dosageKey+fiveDigitsVerhoeff);
        
    }//end:generateControlCode
        
    /**
     * Añade N digitos Verhoeff a una cadena de texto
     * @param value String
     * @param max numero de digitos a agregar
     * @return String cadena original + N digitos Verhoeff
     */
    private String addVerhoeffDigit(String value,int max){
        for(int i=1;i<=max;i++)
            value += Verhoeff.generateVerhoeff(value);            
        return value;
    }
    
    /**
     * Redondea hacia arriba
     * @param value cadena con valor numerico de la forma 123 123.4 123,4
     */
    private String roundUp(String value){        
        //reemplaza (,) por (.)
        value = value.replace(",", ".");
        //redondea a 0 decimales
        BigDecimal valueBD = new BigDecimal(Double.parseDouble(value));
        valueBD = valueBD.setScale(0, BigDecimal.ROUND_HALF_UP);        
        return String.valueOf(valueBD);
    }
    
    /* metodos usados solo para realizar el testeo */
    public String getFiveDigitsVerhoeff() {
        return fiveDigitsVerhoeff;
    }

    public String getStringDKey() {
        return stringDKey;
    }

    public int getSumProduct() {
        return sumProduct;
    }

    public String getBase64SIN() {
        return base64SIN;
    }
    
}//end:class

Test

Impuestos brinda recursos necesarios para verificar la correcta generación del Código de Control.

  • 5000 Casos de prueba para la generación del Código de Control (XLS)
  • 5000 Casos de prueba para la generación del Código de Control (TXT)

Para testear este código haremos uso del archivo de texto con 5000 registros, la clase es la siguiente:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import net.jc_mouse.controlcode.ControlCode;
/**
 * @see https://www.jc-mouse.net/
 * @author mouse
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        
        ControlCode controlCode = new ControlCode();                
        //direccion del archivo de texto
        String fileName = "C:\\Users\\SOYTUBURLA\\Documents\\impuestos\\5000CasosPruebaCCVer7.txt";        
        
        int count=0;        
        int fiveDigitsVerhoeffCount=0;
        int stringDKeyCount=0;
        int sumProductCount=0;
        int base64SINCount=0;
        int ccCount=0;
        
        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = br.readLine()) != null) {
                count+=1;
                //reemplaza "|" por "/-/" por no ser compatible con el metodo split
                line = line.replace("|", "/-/");
                String[] ary = line.split("/-/");
                //genera codigo de control
                String cc = controlCode.generate(ary[0], ary[1], ary[2], ary[3].replace("/", ""), ary[4], ary[5]);                
                //controla errores
                if(!ary[6].equals(controlCode.getFiveDigitsVerhoeff()))fiveDigitsVerhoeffCount+=1;                
                if(!ary[7].equals(controlCode.getStringDKey()))stringDKeyCount+=1;                
                if(!ary[8].equals(String.valueOf(controlCode.getSumProduct())))sumProductCount+=1;                      
                if(!ary[9].equals(String.valueOf(controlCode.getBase64SIN())))base64SINCount+=1;                      
                if(!ary[10].equals(cc))ccCount+=1;
            }
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
                
        System.out.println("Error 5 digitos Verhoeff: " + fiveDigitsVerhoeffCount);
        System.out.println("Error Cadena de dosificación: " + stringDKeyCount);
        System.out.println("Error Suma Producto: " + sumProductCount);
        System.out.println("Error Base64: " + base64SINCount);
        System.out.println("Error codigo de control: " + ccCount);
        System.out.println("---------------------------------------------");
        System.out.println("Total Registros testeados: " + count);        
    }
    
}//end:class

Después de unos segundos dependiendo de la maquina que tengamos veremos algo como esto:

test code

El código funciona 🙂

Proyecto «Control Code»

Documentación PDF

enjoy!!!

Fuente: www.impuestos.gob.bo

Tags

Artículos similares

Pilas con C Sharp (Ejercicio Resuelto)

Desarrolle un programa en consola con un menú para el usuario que permita agregar elementos a una pila, eliminar element[...]

Formulario de autenticación circular

En este tutorial crearemos un formulario de logueo de forma circular usando el lenguaje java y el IDE de Netbeans. Neces[...]

Login estilo Google

Google tiene entre su formulario de autenticación de usuario para sus diferentes servicios (gmail, blogger, youtube, g+)[...]

Contraseñas con emojis en java

Semanas atrás leí un articulo en el que se pensaba implementar los emojis como contraseñas en los dispositivos con andro[...]

Introducción a MariaDB con Java

Maria DB  es un sistema de gestión de bases de datos derivado de MySQL con licencia GPL, pero con un rendimiento similar[...]

Evaluar expresiones matemáticas en Java con JavaScript

Java 8 trae consigo una nueva versión del motor de JavaScript llamada Nashorn, este motor introduce mejoras de rendimien[...]