Número Consecutivo y Clave en la Factura Electrónica en Costa Rica

13
14176

Uno de los problemas iniciales al comenzar a crear el XML de la Factura Electrónica es la estructura de los campos Clave y Número Consecutivo, ya que pueden crear confusión. En este artículo explicamos como crear estos dos valores y también como crear el código de seguridad que forma parte de la clave.

Número Consecutivo

Este número se compone de 4 partes, código de casa matriz o código de sucursal, código de punto de venta, tipo de comprobante o tipo de documento, y número de comprobante. Ahora que significa cada uno:

[display-posts id=”737″ wrapper=”div” image_size=”thumbnail” include_excerpt=”true” excerpt_length=”15″]

Código de casa matriz o código de sucursal: Este es un número que identifica el lugar donde se emiten las facturas, si la factura corresponde a la casa Matriz debe ser 001, este es un número reservado para casa matriz. Del 002 en adelante se utilizan para identificar cada una de las sucursales.

Código de punto de venta: Este es un numero secuencial que identifica cada uno de los puntos de venta de una sucursal, deben de comenzar en 1 por cada sucursal. Esto quiere decir que la sucursal 002 y 003 ambas deben de tener el punto de venta 1.

Tipo de documento: Este valor identifica que tipo de comprobante estamos generando, 01. Factura Electrónica, 02. Nota de Débito, 03. Nota de Crédito, 04. Tiquete Electrónico, 05. Confirmación de aceptación de documento electrónico, 06. Aceptación Parcial, 07. Rechazo de documento electrónico. (Ver encabezados de estos documentos XML)

Número de comprobante: Este es un valor consecutivo que se debe de generar por tipo de documento. Es un valor que se debe administrar propiamente en el punto de venta. Siempre comienza desde 1 por cada punto de venta y por cada tipo de documento. Por ejemplo el punto de venta código 1 comienza una factura electrónica con el número de comprobante 1, el punto de venta 2 comienza con el número de comprobante en 1 y este mismo punto de venta realiza una nota de crédito que también comienza en 1. Por lo tanto, cada punto de venta y cada tipo de documento tienen un numero de consecutivo individual.

[display-posts id=”482″ wrapper=”div” image_size=”thumbnail” include_excerpt=”true” excerpt_length=”15″]

Clave

Este es el valor que más confusión ha generado, principalmente por el código de seguridad. En este artículo te brindamos una sugerencia de como generar este código y que tenga lógica con respecto a los mismos datos de la factura.

Se compone de 8 partes, código de país (3 caracteres), día (2 caracteres), mes (2 caracteres), año (2 caracteres), número de identificación (12 caracteres), numeración consecutiva (20 caracteres), situación del comprobante (1 carácter), código de seguridad (8 caracteres). Ahora que significa cada uno:

Código de país: Es un valor internacional de 3 caracteres que identifica el país, en este caso Costa Rica es 506.

Día: Valor de 2 caracteres que representa el día, ejemplo 01, 10, 24.

Mes: Valor de 2 caracteres que representa el mes, ejemplo 01, 08, 12.

Año: Valor de 2 caracteres que representa el año, en este caso los últimos 2 dígitos del año.

Número de identificación: Este valor debe ser el mismo que está definido como numero de identificación del emisor en la factura electrónica. En caso contrarió Hacienda va a rechazar la factura. Tiene una longitud de 12 caracteres y por ejemplo si el numero de identificación es de 9 los otros caracteres a la izquierda se llenan con 0.

Numeración consecutiva: Este es el valor de 20 caracteres generado arriba, es el número de consecutivo de la factura electrónica.

Situación de comprobante electrónica: Valor de 1 carácter que identifica la situación actual cuando se genera el comprobante, 1. Situación Normal, 2. Contingencia, 3. Sin Internet.

Código de seguridad: Este valor tiene una longitud de 8 caracteres y debe ser generado diferente para cada comprobante. Ahora si bien puede ser un número aleatorio lo ideal es que cuente con un formato estandarizado. Después de haber realizado sistemas de facturas electrónicas para países como Brasil, Perú, Ecuador, Guatemala, Chile, en algunos de estos utilizan un valor similar pero si le han definido un formato, para Costa Rica yo adapté de estos uno propio modificado.

Ejemplo se puede descargar en https://github.com/royrojas/FacturaElectronicaCR

[display-posts id=”737″ wrapper=”div” image_size=”thumbnail” include_excerpt=”true” excerpt_length=”15″]

Generar el código de seguridad

Este ejemplo es una propuesta de como se debería de hacer, pero usted puede tener su propio generador, lo que si recomiendo es que no sea un número random y que tu clave de seguridad tenga una estructura lógica.

Ejemplo C# .Net

        public static string CreaCodigoSeguridad(string TipoComprobante, 
                                                 string Localidad, 
                                                 string CodigoPuntoVenta, 
                                                 DateTime Fecha, 
                                                 string NumeroFactura)
        {
            // 'Los parametros TipoComprobante, Localidad y CodigoPuntoVenta 
            //  pueden modificarse por otros valores, siempre manteniendo la longitud
            // 'Tipo Comprobante debe tener 2 caracteres máximo
            // 'Localidad debe tener 3 caracteres máximo
            // 'Punto de Venta debe de tener 5 caracteres máximo
            // 'Fecha es un campo datetime, debe ser la fecha de la factura
            // 'Número Factura debe tener máximo 10 caracteres 
            //  y debe ser el mismo parámetro que se usa en la 
            //  funcion GeneraNumeroSecuencia
            try
            {
                if (Anno.Trim().Length == 4)
                    Anno = Anno.Substring(2, 2);

                if ((TipoComprobante.Trim().Length > 2))
                {
                    throw new Exception("Tipo Comprobante debe tener 2 caracteres");
                }

                if ((Localidad.Trim().Length > 3))
                {
                    throw new Exception("Localidad no debe de superar los 3 caracteres");
                }

                if ((CodigoPuntoVenta.Trim().Length > 5))
                {
                    throw new Exception("Codigo Punto Venta no debe de superar los 5 caracteres");
                }

                if ((NumeroFactura.Trim().Length > 10))
                {
                    throw new Exception("Numero Factura no de superar los 10 caracteres");
                }

                string concatenado = "";
                concatenado = (concatenado + TipoComprobante.PadLeft(2, '0'));
                concatenado = (concatenado + Localidad.PadLeft(3, '0'));
                concatenado = (concatenado + CodigoPuntoVenta.PadLeft(5, '0'));
                concatenado = (concatenado + Fecha.ToString("yyyyMMddHHmmss"));
                concatenado = (concatenado + NumeroFactura.PadLeft(10, '0'));
                if ((concatenado.Length != 34))
                {
                    throw new Exception("El concatenado debe de ser de 34 caracteres para poder calcular el código de seguridad");
                }

                int calculo = 0;
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(0, 1)) * 3));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(1, 1)) * 2));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(2, 1)) * 9));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(3, 1)) * 8));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(4, 1)) * 7));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(5, 1)) * 6));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(6, 1)) * 5));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(7, 1)) * 4));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(8, 1)) * 3));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(9, 1)) * 2));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(10, 1)) * 9));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(11, 1)) * 8));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(12, 1)) * 7));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(13, 1)) * 6));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(14, 1)) * 5));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(15, 1)) * 4));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(16, 1)) * 3));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(17, 1)) * 2));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(18, 1)) * 9));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(19, 1)) * 8));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(20, 1)) * 7));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(21, 1)) * 6));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(22, 1)) * 5));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(23, 1)) * 4));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(24, 1)) * 3));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(25, 1)) * 2));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(26, 1)) * 9));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(27, 1)) * 8));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(28, 1)) * 7));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(29, 1)) * 6));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(30, 1)) * 5));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(31, 1)) * 4));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(32, 1)) * 3));
                calculo = (calculo
                            + (int.Parse(concatenado.Substring(33, 1)) * 2));
                int mDV = 0;
                int digitoMod = 0;
                digitoMod = (calculo % 11);
                if (((digitoMod == 0)
                            || (digitoMod == 1)))
                {
                    mDV = 0;
                }
                else
                {
                    mDV = (11 - digitoMod);
                }

                return (TipoComprobante.PadLeft(2, '0')
                            + (calculo.ToString().PadLeft(5, '0') + mDV.ToString()));
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }

Ejemplo VB .net

    Public Function CreaCodigoSeguridad(TipoComprobante As String, Localidad As String,
                                        CodigoPuntoVenta As String, Fecha As Date,
                                        NumeroFactura As String) As String

        ''Los parametros TipoComprobante, Localidad y CodigoPuntoVenta pueden modificarse por otros valores, siempre manteniendo la longitud
        ''Tipo Comprobante debe tener 2 caracteres máximo
        ''Localidad debe tener 3 caracteres máximo
        ''Punto de Venta debe de tener 5 caracteres máximo
        ''Fecha es un campo datetime, debe ser la fecha de la factura
        ''Numero Factura debe tener máximo 10 caracteres y debe ser el mismo parámetro que se usa en la funcion CreaNumeroSecuencia
        Try
            If TipoComprobante.Trim.Length > 2 Then Throw New Exception("Tipo Comprobante debe tener 2 caracteres")
            If Localidad.Trim.Length > 3 Then Throw New Exception("Localidad no debe de superar los 3 caracteres")
            If CodigoPuntoVenta.Trim.Length > 5 Then Throw New Exception("Codigo Punto Venta no debe de superar los 5 caracteres")
            If NumeroFactura.Trim.Length > 10 Then Throw New Exception("Numero Factura no de superar los 10 caracteres")

            ''Concatenamos los valores para poder calcular el valor con la fórmula más adelante.
            Dim concatenado As String = ""
            concatenado += TipoComprobante.PadLeft(2, "0")
            concatenado += Localidad.PadLeft(3, "0")
            concatenado += CodigoPuntoVenta.PadLeft(5, "0")
            concatenado += Fecha.ToString("yyyyMMddHHmmss")
            concatenado += NumeroFactura.PadLeft(10, "10")

            If concatenado.Length <> 34 Then Throw New Exception("El concatenado debe de ser de 34 caracteres para poder calcular el código de seguridad")

            Dim calculo As Integer = 0

            calculo += CInt(concatenado.Substring(0, 1)) * 3
            calculo += CInt(concatenado.Substring(1, 1)) * 2
            calculo += CInt(concatenado.Substring(2, 1)) * 9
            calculo += CInt(concatenado.Substring(3, 1)) * 8
            calculo += CInt(concatenado.Substring(4, 1)) * 7
            calculo += CInt(concatenado.Substring(5, 1)) * 6
            calculo += CInt(concatenado.Substring(6, 1)) * 5
            calculo += CInt(concatenado.Substring(7, 1)) * 4
            calculo += CInt(concatenado.Substring(8, 1)) * 3
            calculo += CInt(concatenado.Substring(9, 1)) * 2
            calculo += CInt(concatenado.Substring(10, 1)) * 9
            calculo += CInt(concatenado.Substring(11, 1)) * 8
            calculo += CInt(concatenado.Substring(12, 1)) * 7
            calculo += CInt(concatenado.Substring(13, 1)) * 6
            calculo += CInt(concatenado.Substring(14, 1)) * 5
            calculo += CInt(concatenado.Substring(15, 1)) * 4
            calculo += CInt(concatenado.Substring(16, 1)) * 3
            calculo += CInt(concatenado.Substring(17, 1)) * 2
            calculo += CInt(concatenado.Substring(18, 1)) * 9
            calculo += CInt(concatenado.Substring(19, 1)) * 8
            calculo += CInt(concatenado.Substring(20, 1)) * 7
            calculo += CInt(concatenado.Substring(21, 1)) * 6
            calculo += CInt(concatenado.Substring(22, 1)) * 5
            calculo += CInt(concatenado.Substring(23, 1)) * 4
            calculo += CInt(concatenado.Substring(24, 1)) * 3
            calculo += CInt(concatenado.Substring(25, 1)) * 2
            calculo += CInt(concatenado.Substring(26, 1)) * 9
            calculo += CInt(concatenado.Substring(27, 1)) * 8
            calculo += CInt(concatenado.Substring(28, 1)) * 7
            calculo += CInt(concatenado.Substring(29, 1)) * 6
            calculo += CInt(concatenado.Substring(30, 1)) * 5
            calculo += CInt(concatenado.Substring(31, 1)) * 4
            calculo += CInt(concatenado.Substring(32, 1)) * 3
            calculo += CInt(concatenado.Substring(33, 1)) * 2

            ''Después de calcular el valor, obtenemos el dígito verificador.
            Dim mDV As Integer = 0
            Dim digitoMod As Integer = 0

            digitoMod = calculo Mod 11

            If digitoMod = 0 Or digitoMod = 1 Then
                mDV = 0
            Else
                mDV = 11 - digitoMod
            End If

            ''Retornamos el valor compuesto del tipo de documento, el valor calculado y el digito verificador.
            ''Esto nos retorna un valor congruente y estandarizado.
            Return TipoComprobante.PadLeft(2, "0") & calculo.ToString.PadLeft(5, "0") & mDV.ToString
        Catch ex As Exception
            Throw ex
        End Try
    End Function

Si desea más información del API no dude en contactarnos.

Información: contacto@dotnetcr.com

13 COMMENTS

  1. Buen día, Roy
    Necesito un medio de comunicación directo y más formal, como un número telefónico para hacerle unas preguntas.
    Mi nombre es Ricardo Barquero y mi número telefónico móvil es: 8941-5000.

  2. Buenas tardes Roy, muchas gracias por esta información.

    Una consulta muy rápida con respecto al consecutivo y un escenario hipotético:

    Suponiendo que la primer factura de un cliente es 00100001010000000001, la segunda sería 00100001010000000002, tercera 00100001010000000003 y asi sucesivamente…

    Suponiendo que el cliente emite miles de miles de facturas o tiquetes por día:
    Qué pasará cuando el consecutivo sea 00100001019999999999?

    Debe ser éste consecutivo único siempre siempre? Es decir ,suponiendo que una persona ya ha emitido algunas facturas con el sistema del ATV, y probablemente la primera que emitió tuvo el consecutivo 00100001010000000001… si luego esta misma persona se decide mover a un sistema de terceros para facturar, cómo hacen(los que ofrecen el sistema de facturacion) para saber el consecutivo en el que quedó?

    El sistema de terceros puede volver a generar el consecutivo con 00100001010000000001 y asi empezar a incrementar el #?

    Perdón si la pregunta es un poco tonta o me estoy confundiendo mas de la cuenta con respecto a este consecutivo.

    De antemano gracias.

    • Cuando llegas al numero 9999999999 debes regresar al consecutivo 1.
      Si cambias de proveedor de factura, en este nuevo sistema debes de continuar en el consecutivo donde quedaste, si quedaste en la factura 10, en el nuevo sistema debes seguir con el 11.

  3. Hola Roy,
    Primero muchas gracias por toda la ayuda que nos brindas.

    Te comento, tengo un problemilla con un cliente que me rechazo la factura electronica(la factura fue aceptada por tributacion y le llego al correo correctamente),
    segun el porque en el pdf no aparece el monto en letras ni el tipo de cambio de la factura.

    existe un documento de tributacion que liste los datos que deben de aparecer en el pdf??

    para asi yo saber si realmente tengo la obligacion de agregar esos campos al pdf
    Gracias

    • Si hiciste la factura en dólares, seria bueno que pongas el tipo de cambio aunque no es requerido. Hacienda no indica ningún requerimiento en los documentos impresos, mas que tengan el consecutivo y la clave visibles.

  4. Hola!

    Un consulta: existe algún modo (API) para obtener el consecutivo actual? Específicamente, quisiera saber si hay alguna forma de saber cuál debería ser el siguiente consecutivo de la siguiente factura en caso de que no se sepa cuál es sea cual fuere la razón. Hacienda ofrece alguna manera para saber cuál fue el último consecutivo recibido?

  5. Muchas gracias Roy, vi esa opción sin embargo no parece ser muy apropiada debido a que, contrario a lo dice la documentación, se retornan los documentos ordenados por fecha ASCENDENTEMENTE, por lo que para saber cuáles son los últimos consecutivos habría que hacer X muchas solicitudes al API para obtener todos los registros y poder encontrar los últimos consecutivos usados. Supongamos que necesito saber los últimos consecutivos para Factura electronica, Nota de crédito y Nota de débito, tendría que comenzar a hacer solicitudes con offset=0 y limit=50 (50=>max).

    Para colmo no hay opción de filtrar los resultados por tipo de documento! 🙁

    • La opcion de la clave no es para facilitar búsquedas, en teoría es para añadir un nivel de seguridad y poder dar trazabilidad a tus códigos. Aunque aquí en CR no se esté utilizando bien, asi como lo pongo yo es como se utiliza en muchos paises que nos llevan años con el tema de la FE.

  6. Buenas tardes Roy, el caso mio en particular dejé de utilizar un sistema de terceros para factura e inicie con el ATV de Hacienda, este sistema gratuito de Hacienda no me permite modificar el número de consecutivo de la factura. Que sucede en este caso? iniciaría con el consecutivo 00100001010000000001, aunque no había quedado en ese consecutivo anteriormente.

LEAVE A REPLY

Please enter your comment!
Please enter your name here