¿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
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:
Para generar un Código de Control, se requiere de la siguiente información
Datos de dosificación:
Datos de la transacción comercial:
Llave de Dosificación
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»
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.
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:
El código funciona 🙂
Proyecto «Control Code»
Documentación PDF
enjoy!!!
Fuente: www.impuestos.gob.bo
Desarrolle un programa en consola con un menú para el usuario que permita agregar elementos a una pila, eliminar element[...]
En este tutorial crearemos un formulario de logueo de forma circular usando el lenguaje java y el IDE de Netbeans. Neces[...]
Google tiene entre su formulario de autenticación de usuario para sus diferentes servicios (gmail, blogger, youtube, g+)[...]
Semanas atrás leí un articulo en el que se pensaba implementar los emojis como contraseñas en los dispositivos con andro[...]
Maria DB es un sistema de gestión de bases de datos derivado de MySQL con licencia GPL, pero con un rendimiento similar[...]
Java 8 trae consigo una nueva versión del motor de JavaScript llamada Nashorn, este motor introduce mejoras de rendimien[...]