Generador de identificadores secuenciales con reinicio anual: compatibilidad con múltiples prefijos

Prev Next

To download the Yearly Resetting Sequential ID Generator - Multi-Prefix Support, visit: [tulip](https://<span translate=).co/library/apps/yearly-resetting-sequential-id-generator---multi-prefix-support/">Library

En este artículo, uno de nuestros desarrolladores de la biblioteca, Tamás, explica las decisiones de desarrollo que hay detrás de la aplicación de bloques de construcción «Generador de ID secuenciales con reinicio anual: compatibilidad con múltiples prefijos», disponible en la biblioteca.

Por qué la diseñé así

Diseñé este bloque lógico para resolver un problema muy práctico: generar identificadores de forma fiable, fácil de reutilizar y rápida de implementar en numerosas aplicaciones.Mi objetivo era que el propio identificador contuviera todo lo necesario para calcular el siguiente. Cada parte del identificador tiene una función clara:* el prefijo indica a qué secuencia pertenece el identificador* el año separa la secuencia a lo largo del tiempo* el número muestra el orden dentro de ese año

Gracias a esta estructura, no tuve que introducir ninguna lógica de reinicio. El último identificador existente ya contiene toda la información necesaria para generar el siguiente. Mientras el último identificador sea válido, el siguiente siempre se puede calcular de forma predecible.

Incluí el año directamente en el ID porque separa de forma natural los registros a lo largo del tiempo. Además, permite que la numeración se reinicie automáticamente cada año sin necesidad de ningún reinicio programado ni actualización manual.

El prefijo actúa como límite entre las diferentes secuencias. Esto permite ejecutar varias secuencias de numeración independientes dentro de la misma tabla. Cambiar el prefijo simplemente significa continuar con una secuencia diferente.

He añadido una validación porque todo el enfoque depende de una estructura de ID coherente. Si un ID existente no sigue el formato esperado, continuar la secuencia podría dar lugar a resultados confusos o incoherentes. Prefiero detenerme a tiempo, hacer visible el problema y evitar generar ID incorrectos.

Elegí una búsqueda basada en conectores para que el bloque lógico pueda funcionar con diferentes tablas sin cambiar la lógica principal. Una vez configurado el conector, solo hay que proporcionar el «tableId» y el prefijo, y el mismo bloque lógico se puede reutilizar en varios lugares.

Para mí, el objetivo no era solo generar identificadores, sino crear un patrón reutilizable en el que el propio formato del identificador defina cómo funciona la secuenciación. Eso es lo que mantiene la lógica sencilla, predecible y fácil de reutilizar en diferentes aplicaciones.

Qué hace este bloque lógico

Este bloque lógico genera identificadores secuenciales que se reinician anualmente con la siguiente estructura:prefijo + año + número consecutivo

Ejemplo: TEST20240001 TEST20240002

Cuando cambia el año, la numeración se reinicia automáticamente desde 1: TEST20250001

Pueden existir varios prefijos en la misma tabla. Cada prefijo mantiene su propia secuencia independiente: ORD20240001 ORD20240002 INV20240001 INV20240002

El bloque lógico garantiza: * un formato de ID coherente * un reinicio anual automático * una numeración independiente por prefijo * la detección temprana de ID históricos con formato incorrecto * la detección temprana de desbordamiento numérico

Por qué el conector forma parte del diseño

Antes de generar el siguiente identificador, el bloque lógico necesita el último identificador existente para el prefijo dado. El conector realiza una búsqueda filtrada: * filtra los registros cuyo identificador comience por el prefijo; * ordena en orden descendente por _sequenceNumber; * devuelve el último registro

Dado que el prefijo forma parte del filtro, cada prefijo tiene su propio contador incremental, incluso dentro de la misma tabla. Esto mantiene las secuencias aisladas y predecibles.

Cómo está estructurada la implementación

La implementación real utiliza tres desencadenadores. Esa estructura es intencionada. Quería que el bloque lógico fuera fácil de seguir de arriba abajo, al tiempo que se mantenía la lógica principal separada y reutilizable. Así que, en lugar de repartir la lógica entre muchos desencadenadores editables, la organicé en tres etapas:1. CONFIGURACIÓN DEL USUARIO: ETAPA DE ENTRADA 2. Validación «MAGIC» + generación de ID 3. CONFIGURACIÓN DEL USUARIO: ETAPA DE SALIDA

Esto proporciona un buen equilibrio entre usabilidad y control.* El primer disparador es donde se definen las entradas.* El segundo disparador contiene la lógica interna que, normalmente, no es necesario editar para que el bloque lógico funcione.* El tercer disparador es donde se decide qué hacer con el resultado generado.

Esto significa que, normalmente, no es necesario intervenir en el disparador de la lógica central. Solo hay que configurar el lado de entrada y el de salida. Esa fue una de las decisiones clave de diseño: hacer que el bloque lógico sea reutilizable exponiendo únicamente las partes que suelen necesitar cambios.

Desencadenador 1 – CONFIGURACIÓN DEL USUARIO: ETAPA DE ENTRADA

Este es el primer disparador con el que interactúas. Su finalidad es definir los valores de entrada que necesita el bloque lógico antes de que se pueda generar cualquier ID. En la práctica, este disparador se utiliza para definir:* El prefijo: _configPrefix* La longitud del ID: _configIdNumberFormat* El ID de la tabla pasado al conector: tableId

Qué significa cada valor

**_configPrefix:**es el texto que se coloca al principio de cada ID generado. Se utiliza para definir qué secuencia se desea generar.

Ejemplos:* ORD* INV* TEST

Si _configPrefix = ORD, el bloque lógico solo tiene en cuenta los ID existentes que empiezan por ORD y continúa únicamente con esa secuencia. Por lo tanto, este valor realiza dos funciones al mismo tiempo:* define el prefijo visible en el ID final* define qué ID históricos debe utilizar el conector como fuente para el siguiente número

Esto significa que prefijos diferentes en la misma tabla producen secuencias diferentes**._configIdNumberFormat**

Esto define la longitud de la parte numérica del ID. Se configura como una cadena rellenada con ceros. Ejemplos:000000000000000000

El número de ceros define la anchura de la sección numérica.

Ejemplos:0000 → 0001, 0002, 0003 ... 9999 000000 → 000001, 000002 ... 999999

Este valor no establece el número real. Define el formato al que debe ajustarse el número. Lo utilizo con dos fines: * para añadir ceros a la izquierda del número generado * para comprobar si el siguiente número sigue ajustándose a la longitud permitida

tableId

Esto define qué tabla debe consultar el conector.

En la práctica, esto es lo que hace que el bloque lógico sea reutilizable en diferentes tablas. Puedes copiar el mismo bloque lógico y dirigirlo a otra tabla cambiando únicamente este valor en el disparador de configuración.

El bloque lógico no busca tablas por sí mismo. Pasa tableId y _configPrefix al conector, y este devuelve el último ID coincidente de esa tabla.

Cómo utilizarlos conjuntamente

Estos tres valores definen conjuntamente el comportamiento del generador:* tableId indica al bloque lógico dónde buscar* _configPrefix le indica qué secuencia debe continuar* _configIdNumberFormat le indica cómo debe verse la parte numérica

Guía práctica de configuración

Al configurar el bloque lógico, asegúrate de que:* tableId apunte a la tabla correcta* _configPrefix coincida con el prefijo que deseas continuar* _configIdNumberFormat coincida con la longitud numérica que deseas aplicar

Si cambia el prefijo, ya no continuará con la secuencia anterior. Estará iniciando o continuando una secuencia diferente específica de ese prefijo. Si aumenta posteriormente la longitud del formato numérico, el bloque lógico esperará ese formato más amplio al validar los ID existentes en esa tabla y en el ámbito del prefijo.El conector recupera el último ID que coincide con el prefijo y lo almacena en _stateLastId. Si aún no existe ningún ID, LINK() garantiza que el generador pueda seguir produciendo el primer ID válido.

Por qué el conector forma parte del diseño

El conector no es solo un detalle técnico. Forma parte de la estrategia de reutilización. Utilizo el conector para leer el último ID existente de la tabla de destino. El conector recibe el ID de la tabla como entrada, aplica el filtro de prefijo, ordena los registros por _sequenceNumber en orden descendente y devuelve el último registro que coincide.

Esto es importante por dos razones.

En primer lugar, permite que el bloque lógico continúe la numeración a partir del estado real de la tabla.

En segundo lugar, facilita mucho la reutilización del bloque lógico. Si copias el botón a otra aplicación, no es necesario reescribir la lógica. Solo tienes que dirigir el conector a la tabla correcta y proporcionar la configuración necesaria. Este es el patrón práctico que subyace a la creación:* crear el botón una sola vez* configurar la entrada de la tabla en el conector* definir el prefijo y el formato numérico* reutilizarlo donde sea necesario

Esa fue una elección deliberada para facilitar la creación más rápida de aplicaciones.

¿Qué hace el conector en el bloque lógico?

Esto es lo que ocurre aquí:* el conector recibe el ID de la tabla* el conector recibe el prefijo* el conector filtra los registros por prefijo* el conector ordena los registros por _sequenceNumber en orden descendente* el conector devuelve el último ID coincidente

El conector también admite un caso de uso muy importante: varias entidades en la misma tabla.

Si una misma tabla contiene registros como: ORD20240001 MAT20240011

entonces el bloque lógico no debe tratarlos como una secuencia compartida. Cada prefijo debe continuar únicamente a partir de su propio valor más reciente.

Por lo tanto, el conector filtra primero por prefijo. Eso significa que los siguientes valores pasan a ser: ORD → ORD20240002 MAT → MAT20240012

He elegido este modelo porque pueden coexistir diferentes entidades en la misma tabla, pero eso no significa que deban compartir el mismo número consecutivo.

También conviene dejar claro un requisito de configuración: el conector necesita autenticación, por lo que hay que generar un token y añadirlo a la configuración de autenticación del conector. Considero que esto forma parte del trabajo de configuración, no de la lógica del bloque lógico en sí, pero sigue siendo un aspecto importante para que el bloque lógico sea utilizable.

Desencadenador 2 – MAGIC: validación + generación de ID

Este disparador constituye la lógica central del bloque lógico. Realiza tres acciones en orden: 1. comprueba si el último ID coincidente tiene la estructura esperada; 2. comprueba si el siguiente número seguiría ajustándose a la longitud numérica configurada; 3. genera el siguiente ID

He agrupado estos pasos en un único disparador porque pertenecen al mismo flujo de decisión. Antes de crear un nuevo ID, quiero responder a tres preguntas: * ¿Es seguro utilizar el ID anterior desde el punto de vista estructural? * ¿El siguiente número seguiría ajustándose al formato? * En caso afirmativo, ¿cuál debería ser el siguiente ID?

Validación del formato

Expression

REGEX_REPLACE(LINK(Variable._stateLastId.id, Variable._configPrefix + DATETIMETOTEXT(App info.Current Date and Time, 'YYYY') + Variable._configIdNumberFormat), '^' + Variable._configPrefix + '[0-9]{4}' + '[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$','ok')

Qué hace este expression

Este « expression » comprueba si el último ID coincide exactamente con la estructura que espera el bloque lógico. Funciona en este orden: 1. LINK(...) toma Variable._stateLastId.id cuando el conector devuelve un valor. 2. Si aún no existe ningún ID, LINK(...) utiliza un valor de reserva construido a partir de: * Variable._configPrefix * DATETIMETOTEXT(...,'YYYY') * Variable._configIdNumberFormat3. Ese valor de reserva se incluye para que la validación siga teniendo una estructura válida que evaluar, incluso cuando se trate del primer ID generado.4. Variable._configPrefix define el prefijo exacto con el que debe comenzar el ID. 5. DATETIMETOTEXT(...,'YYYY') no se utiliza como valor de coincidencia dentro de la propia expresión regular, pero forma parte de la estructura de reserva para que esta tenga el aspecto de un ID real. 6. LEN(Variable._configIdNumberFormat) define cuántos dígitos debe contener la parte numérica.7. A continuación, REGEX_REPLACE(...) compara la cadena completa con este patrón: * prefijo * exactamente 4 dígitos para el año * exactamente N dígitos para la parte numérica8. Si la cadena completa coincide, todo el valor se sustituye por «ok».9. Si no coincide, el valor original permanece sin cambios.10. A continuación, el disparador comprueba si el resultado es igual a «ok». Si no lo es, la ejecución se detiene.

Por lo tanto, este « expression » no extrae valores para realizar cálculos. Su función es verificar que el ID tenga la estructura esperada antes de que se aplique cualquier lógica numérica.

Por qué elegí esto

Decidí validar la estructura completa del ID antes de aplicar cualquier lógica numérica. Lo diseñé así porque el resto del disparador asume posiciones fijas:* prefijo al principio* año inmediatamente después del prefijo* número inmediatamente después del año

Si el ID anterior no se ajusta a ese esquema, incrementarlo a partir de ahí no sería fiable.

También he optado por incluir aquí la alternativa LINK(...) para que el primer ID generado no necesite una lógica de validación independiente. La misma ruta de validación funciona tanto si existe un ID anterior como si no. Detengo intencionadamente el disparador en lugar de intentar reparar los ID mal formados. La corrección automática ocultaría los problemas de datos y podría crear una secuencia que pareciera válida pero que se basara en una entrada errónea.

Protección contra desbordamiento

Expression

LEN(IF(MID(LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), LEN(Variable._configPrefix), 4) = DATETIMETOTEXT(App info.Fecha y hora actuales,'AAAA'),RIGHT(Variable._configIdNumberFormat + TOTEXT(TEXTTOINTEGER(REGEX_REPLACE(LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat),'^' + Variable._configPrefix + '[0-9]{4}','')) + 1), LEN(Variable._configIdNumberFormat)), RIGHT(Variable._configIdNumberFormat + '1', LEN(Variable._configIdNumberFormat))))

Qué hace este expression

Esta función « expression » calcula cuál sería la siguiente parte numérica y, a continuación, mide su longitud. Funciona en este orden: 1. LINK(...) utiliza el último ID cuando existe uno. Si no es así, recurre a Variable._configPrefix + Variable._configIdNumberFormat. 2. MID(..., LEN(Variable._configPrefix), 4) lee los 4 caracteres inmediatamente posteriores al prefijo. Estos se consideran la parte correspondiente al año del ID anterior. 3. DATETIMETOTEXT(...,'YYYY') proporciona el año actual para la comparación. 4. IF(...) decide si la parte numérica debe continuar o reiniciarse: * mismo año → incremento * año diferente → empezar desde 15. En la rama de incremento, REGEX_REPLACE(...) elimina el prefijo y el año de 4 dígitos del inicio del ID anterior, de modo que solo queda la parte numérica.6. TEXTTOINTEGER(...) + 1 convierte ese texto numérico en un número y lo incrementa.7. TOTEXT(...) vuelve a convertir el nuevo número en texto.8. Variable._configIdNumberFormat + ... antepone el formato de ceros configurado al valor.9. RIGHT(..., LEN(Variable._configIdNumberFormat)) conserva solo el número de dígitos requerido.10. En la rama de reinicio, RIGHT(Variable._configIdNumberFormat + '1', LEN(...)) crea el primer número válido para un nuevo año.11. LEN(...) mide la longitud de la parte numérica resultante.

El disparador compara esta longitud con la longitud permitida de Variable._configIdNumberFormat. Si la siguiente parte numérica fuera demasiado larga, la ejecución se detiene.

Por qué elegí esto

Decidí calcular el siguiente número de la misma forma en que se genera realmente el ID, en lugar de utilizar una fórmula independiente para estimar cuándo el número superaría la longitud permitida.

Lo diseñé así porque quería que la comprobación de desbordamiento siguiera exactamente las mismas reglas que la generación real del ID. Eso reduce la probabilidad de que la lógica de validación y la lógica de generación se desajusten más adelante.

Detener el proceso aquí es más seguro que continuar. Si el siguiente valor numérico ya no se ajusta a la longitud configurada, el ID resultante rompería la estructura que has configurado.

Por ejemplo, si el formato numérico es 0000, entonces 9999 es válido, pero 10000 no lo es. Quiero que el bloque lógico detecte eso antes de la creación del registro, no después.

Lógica de generación de ID

Expression

Variable._configPrefix + DATETIMETOTEXT(App info.Fecha y hora actuales,'AAAA')+ IF(MID(LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat),LEN(Variable._configPrefix),4) = DATETIMETOTEXT(Información de la aplicación.Fecha y hora actuales, 'AAAA'), RIGHT(Variable._configIdNumberFormat + TOTEXT(TEXTTOINTEGER(REGEX_REPLACE(LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat),'^' + Variable._configPrefix + '[0-9]{4}','')) + 1), LEN(Variable._configIdNumberFormat)), RIGHT(Variable._configIdNumberFormat + '1', LEN(Variable._configIdNumberFormat)))

Qué hace este « expression »

Esta es la función « expression » que genera el ID final. Funciona en este orden: 1. Variable._configPrefix añade el prefijo configurado al principio del ID. 2. DATETIMETOTEXT(App info.Current Date and Time,'YYYY') añade el año actual de 4 dígitos. 3. LINK(...) utiliza el último ID devuelto por el conector, si existe. Si el conector no devuelve un ID, utiliza Variable._configPrefix + Variable._configIdNumberFormat como estructura alternativa. 4. MID(..., LEN(Variable._configPrefix), 4) lee los 4 caracteres inmediatamente posteriores al prefijo. Estos se tratan como la parte correspondiente al año del ID anterior.5. Aquí se vuelve a utilizar DATETIMETOTEXT(...,'YYYY') para que el « expression » pueda determinar si el ID anterior pertenece al año actual.6. IF(...) controla las dos rutas posibles: * mismo año → continuar con la numeración * año diferente → restablecer a 17. En la rama del mismo año, REGEX_REPLACE(...) elimina el prefijo y el año de 4 dígitos del principio del ID anterior, de modo que solo queda la parte numérica.8. TEXTTOINTEGER(...) + 1 ** incrementa ese valor numérico.9. TOTEXT (...) vuelve a convertir el valor incrementado en texto.10. Variable._configIdNumberFormat + ... antepone el formato de ceros configurado al nuevo valor.11. RIGHT (..., LEN(Variable._configIdNumberFormat)) conserva solo el número de dígitos requerido, de modo que la parte numérica siempre tiene una longitud fija.12. En la rama de reinicio, RIGHT (Variable._configIdNumberFormat + '1', LEN(...))** crea el primer número de una nueva secuencia anual.13. El prefijo, el año actual y el número formateado se concatenan para formar el ID final.

Así, el resultado final siempre tiene esta estructura:prefijo + año + número de longitud fija

Por qué elegí esto

He optado por derivar el siguiente ID íntegramente a partir del último ID real de la tabla. Lo he diseñado así porque el ID anterior ya contiene todo lo que necesita la lógica:* a qué prefijo pertenece la secuencia* a qué año pertenece* cuál fue el último valor numérico

Esto significa que no necesito una tabla de contadores independiente, un campo de año almacenado ni un reinicio anual programado.

También he optado por un formato de longitud fija porque mantiene la estructura predecible y facilita la validación.

La contrapartida es que el « expression » depende de una estructura de ID estricta. Por eso el paso de validación es lo primero. Acepté esa contrapartida porque un contrato estricto y legible resulta más útil aquí que intentar admitir muchos formatos de entrada poco definidos.

Desencadenante 3 – CONFIGURACIÓN DEL USUARIO: ETAPA DE SALIDA

Este es el último disparador del bloque lógico. Su finalidad es definir qué debe suceder con el ID generado una vez que _stateOutput contenga un valor válido.

Lo importante aquí es que esta etapa se ha dejado intencionadamente abierta para que la decidas tú.

Si _stateOutput no está en blanco, puedes decidir qué debe hacer a continuación el bloque lógico. En la configuración actual, el desencadenador muestra un mensaje de orientación que explica los siguientes pasos: * añadir la tabla configurada a la aplicación * sustituir el paso de mensaje por un desencadenador «Crear registro» * utilizar _stateOutput como el nuevo ID del registro

Lo he diseñado así porque el comportamiento de la salida puede variar según los casos de uso.

Es posible que desees: * crear un registro inmediatamente; * almacenar primero el ID en otra variable; * utilizar el ID dentro de un flujo de trabajo más extenso; * validar condiciones adicionales antes de escribir en la tabla

En lugar de establecer un comportamiento de salida fijo, he mantenido la etapa de salida configurable.

Por eso la configuración visible para el usuario se encuentra en ambos extremos del bloque lógico:* la estructura de entrada es configurable* el comportamiento de salida es configurable* la lógica central permanece en el centro

Enfoque para el nombre de las variables

Las variables internas utilizan un prefijo de guión bajo:* _configPrefix* _configIdNumberFormat* _stateLastId* _stateOutput

Esto te ayuda a distinguir rápidamente entre los valores de configuración y las variables de procesamiento interno.

Límites de diseño

El bloque lógico asume esta estructura:prefijo + año + número. Cada prefijo mantiene su propia secuencia independiente.

El bloque lógico no admite un contador compartido entre varios prefijos.

Si se cambian de posición las secciones del año o numéricas dentro del ID, el expressions debe ajustarse en consecuencia.

Resumen de las decisiones de diseño

Si reduzco todo el bloque lógico a las decisiones fundamentales que lo sustentan, estas son las siguientes:* Quería una reutilización rápida, por lo que el bloque lógico se basa en la configuración.* Quería que la tabla fuera la fuente de verdad, de modo que el siguiente ID se base siempre en el último registro real.* Quería que la lógica fuera fácil de mantener, por lo que el cálculo se centraliza en un único lugar.* Quería que varias entidades en una misma tabla funcionaran correctamente, por lo que cada prefijo tiene su propia secuencia independiente.* Quería que la estructura del ID se mantuviera coherente a lo largo del tiempo, por lo que el año forma parte del ID y la secuencia se reinicia automáticamente cuando cambia el año.* Quería proteger la tabla de patrones incorrectos, por lo que la validación de formato y las comprobaciones de desbordamiento impiden que se generen ID incorrectos antes de que se cree un registro.* Quería que el flujo del disparador fuera legible, por lo que las responsabilidades se han separado en pasos claros: configuración, validación, generación y salida.

Por eso el bloque lógico tiene este aspecto. No es solo un generador de ID. Es un patrón reutilizable para la creación controlada de ID que intenta equilibrar la simplicidad, la reutilización y la protección de datos, al tiempo que mantiene la segmentación anual integrada directamente en la estructura del ID.

Participa

Únete a la comunidad para compartir mejoras, proponer unidades adicionales, informar de problemas o debatir decisiones arquitectónicas.