To download the Sequential ID Generator, visit: [tulip](https://<span translate=).co/library/apps/sequential-id-generator---multiple-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 secuencial de identificadores», disponible en la biblioteca.
Por qué la diseñé así
Quería que este bloque lógico resolviera un problema muy práctico: generar identificadores de una forma fiable, fácil de reutilizar y rápida de implementar en muchas aplicaciones.
El objetivo de diseño más importante no era simplemente «generar el siguiente número». El verdadero objetivo era crear algo que, una vez definido, pudiera copiarse y reutilizarse con muy poco esfuerzo. Si un botón se crea una vez de forma clara, debería ser posible colocarlo en otra aplicación u otro paso y limitarse a ajustar la configuración. No quería que cada creador de aplicaciones tuviera que volver a abrir la lógica y reescribir los desencadenantes cada vez.
Por eso el bloque lógico está diseñado para dar prioridad a la configuración frente a la lógica. Cuando utilizas este bloque lógico, lo que más tienes que tener en cuenta es:* qué prefijo se necesita* qué longitud debe tener la parte numérica* de qué tabla debe leer el botón
Una vez definidos el prefijo, el formato numérico y la tabla, los bloques lógicos ya se encargarán del resto: leer el último ID, validarlo, generar el siguiente valor y comprobar si el número sigue ajustándose a la longitud configurada.
La propia tabla debe ser la fuente de verdad. Un contador independiente (que puede ser una variable, por ejemplo) puede desviarse de los datos reales si se insertan, eliminan o editan registros de forma inesperada. Leer desde la tabla mantiene la numeración vinculada a lo que realmente existe.
Otra decisión importante fue mantener la lógica de cálculo principal en un solo lugar. No quería una rama de disparador para el primer ID, otra rama para el siguiente ID y, además, pequeños fragmentos de disparador adicionales que luego resultaran difíciles de seguir. Quería que la lógica principal se encontrara en un único « expression », de modo que el comportamiento se mantuviera coherente y cualquiera que lo modificara más adelante supiera exactamente dónde buscar.
Así pues, el bloque lógico se basa realmente en unas cuantas decisiones prácticas:* fácil de copiar y reutilizar* lo suficientemente seguro como para proteger la tabla de patrones de ID erróneos* sencillo de configurar* lógica centralizada para que el mantenimiento siga siendo manejable
Aspecto del ID
Cada ID generado consta de dos partes:prefijo + secuencia numérica
Ejemplos:* ORD00000125* MAT00000003* TEST00004567
El prefijo nos indica de qué tipo de registro se trata. La parte numérica es la secuencia continua. Opté por mantener la configuración del formato numérico muy sencilla. En lugar de pedir al usuario un número concreto, como «8 dígitos», el bloque lógico utiliza un patrón de ceros, por ejemplo: * 00000000
Esto resulta más fácil de leer de un vistazo. Si alguien ve ocho ceros, entiende inmediatamente que la sección numérica debe tener siempre ocho caracteres. Por eso mantuve idNumberFormat en este formato en lugar de sustituirlo por un campo de longitud pura.
Ejemplos:* 00000000 → número de 8 dígitos* 0000 → número de 4 dígitos
Es una decisión menor, pero importante. Hace que el bloque lógico resulte más accesible para quienes no quieren pensar en términos de parámetros técnicos.
Cómo está estructurada la compilación
La implementación real utiliza tres desencadenantes. 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 desencadenantes editables, la organicé en tres etapas:1. CONFIGURACIÓN DEL USUARIO: ETAPA DE ENTRADA 2. MAGIA 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 principal. 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* el formato del número de identificación (idNumberFormat)* el ID de la tabla que se pasa al conectorEjemplo:prefijo = ORDidNumberFormat = 00000000ID de la tabla = tabla de destino
Esta etapa es intencionadamente editable, ya que estos son los valores que normalmente varían de un caso de uso a otro.
Puedes coger el mismo botón, copiarlo en otra aplicación o paso, actualizar estos valores de configuración y reutilizar el bloque lógico sin tocar la lógica interna.
Esto permite una implementación más rápida, ya que, una vez definidos los valores de entrada, el resto del flujo funciona automáticamente. Este disparador también ejecuta la llamada al conector que lee el último registro coincidente de la tabla seleccionada.
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 coincidente. 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 en 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: 1. crear el botón una vez; 2. configurar la entrada de la tabla en el conector; 3. definir el prefijo y el formato numérico; 4. 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 que coincide
El conector también admite un caso de uso muy importante: varias entidades en la misma tabla.
Si la misma tabla contiene registros como: ORD00000011 MAT00000003, 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 → ORD00000012, MAT → MAT00000004
He elegido este modelo porque en una misma tabla pueden coexistir diferentes entidades, pero eso no significa que deban compartir el mismo número progresivo.
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.
Lo he mantenido en la etapa de entrada porque pertenece a la configuración y a la recopilación de contexto. Antes de que la lógica interna pueda funcionar, el bloque lógico primero necesita saber con qué está trabajando.
Desencadenante 2 – MAGIC
Este es el disparador de la lógica principal. Lo he llamado así a propósito. La idea es sencilla: esta es la parte que, normalmente, no deberías tener que editar. El bloque lógico ya debería incorporar esta lógica de por sí. Este disparador toma los valores de entrada configurados y el último ID devuelto por el disparador anterior, y a continuación lleva a cabo la toma de decisiones interna. Aunque se trata de un solo disparador, en su interior ocurren varias cosas.
1. Validación del formato: la parte «if» del disparador
La primera comprobación verifica que el último ID devuelto por la tabla siga coincidiendo con la estructura esperada. Estructura esperada: **prefijo + número de longitud fija
Ejemplo de valor válido: ORD00000025 Ejemplos de valores no válidos: ORD25, ORD-00025, ABC00000025, ORD00025A
Expression
REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix + '[0-9]{' + LEN(Variable._configIdNumberFormat) + '}$', 'ok')
Qué hace esta « expression »
Esta expresión « expression » comprueba si el último ID sigue exactamente la estructura que espera el bloque lógico. Lo hace en tres partes:* LINK(...) decide qué valor se debe comprobar. Si el conector no devuelve un ID, se utiliza en su lugar la segunda parte. Esa segunda parte es la estructura inicial configurada, y permite al bloque lógico generar el primer ID para una nueva secuencia de prefijos.* «^» + Variable._configPrefix significa que el valor debe comenzar con el prefijo configurado.* «[0-9]{» + LEN(Variable._configIdNumberFormat) + «}$» significa que el valor debe terminar con exactamente el número de dígitos esperado, y no se permite nada adicional después de eso.
Si todo el valor se ajusta a la estructura esperada, el resultado pasa a ser «ok». Si no se ajusta, el resultado « expression » sigue siendo distinto de «ok», y el disparador detiene el flujo de generación y muestra un mensaje de error
Por qué elegí esto
Añadí esta comprobación porque no quería que el bloque lógico continuara a partir de un ID mal formado. Si el último valor almacenado es incorrecto y la lógica lo acepta, el patrón erróneo puede empezar a propagarse a registros futuros. Por eso quería que la primera función del disparador MAGIC fuera detenerlo desde el principio. Si el formato es incorrecto: * se borra _stateOutput * se muestra un mensaje explicando por qué se ha detenido la generación
2. Validación de desbordamiento
La segunda comprobación analiza el siguiente valor numérico y verifica que sigue cabiendo dentro de la longitud numérica configurada.
Ejemplo: idNumberFormat = 0000
Rango permitido: de 0000 a 9999
Si el siguiente valor fuera: 10000, entonces ya no se ajustaría a la estructura configurada.
Expression
LEN(Variable._configIdNumberFormat) < LEN( TOTEXT( TEXTTOINTEGER( REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix, '' ) ) + 1 ))
Qué hace esta « expression »
Esta expresión « expression » compara dos longitudes:* la longitud numérica permitida, tomada de idNumberFormat* la longitud del siguiente número tras incrementar el valor actual
El lado derecho funciona así:* LINK(...) utiliza el ID devuelto por el conector cuando existe uno. Si el conector no devuelve un ID, utiliza en su lugar la estructura inicial configurada. Esto es lo que hace posible la generación del primer ID.* REGEX_REPLACE(...) elimina el prefijo para que solo quede la parte numérica* TEXTTOINTEGER(...) + 1 calcula el siguiente valor numérico* TOTEXT(...) vuelve a convertir ese siguiente valor en texto* LEN(...) mide cuántos dígitos necesita ese siguiente valor
Si el número siguiente necesita más dígitos de los que permite el formato, la condición se cumple y el disparador detiene el proceso.
Por qué elegí esto
Decidí detener el proceso aquí en lugar de cambiar automáticamente la estructura. Esa decisión fue importante porque quería que la longitud del ID se mantuviera estable a menos que alguien cambiara explícitamente la configuración. Si se detecta un desbordamiento:* Se borra _stateOutput* Se muestra un mensaje explicando que se ha alcanzado la longitud máxima
3. Generación de ID
Si se superan ambas comprobaciones, el disparador genera el siguiente ID. Ejemplo: ORD00000015 → ORD00000016
Este mismo « expression » también gestiona el caso en el que aún no existe ningún registro para ese prefijo. En ese caso, la lógica recurre a la estructura inicial configurada y genera el primer valor de la secuencia. Ejemplo: ORD00000001
Expression
Variable._configPrefix + RIGHT( Variable._configIdNumberFormat + TOTEXT( TEXTTOINTEGER( REGEX_REPLACE( LINK(Variable._stateLastId.id, Variable._configPrefix + Variable._configIdNumberFormat), '^' + Variable._configPrefix, '' ) ) + 1 ), LEN(Variable._configIdNumberFormat))
Qué hace este « expression »
Esta es la « expression » que genera el ID final. Funciona en este orden:* LINK(...) utiliza el último ID devuelto por el conector, si existe. Si el conector no devuelve un ID, utiliza en su lugar la estructura inicial configurada. Esto es lo que permite que la misma « expression » gestione también el primer ID generado.* REGEX_REPLACE(...) elimina el prefijo para que se pueda procesar la parte numérica* TEXTTOINTEGER(...)+ 1 incrementa el número* TOTEXT(...) vuelve a convertir el nuevo número en texto* Variable._configIdNumberFormat + ... añade el patrón de ceros delante del nuevo valor* RIGHT(..., LEN(Variable._configIdNumberFormat)) conserva solo el número de dígitos requerido* Variable._configPrefix + ... vuelve a añadir el prefijo para crear el ID definitivo. El resultado se almacena en _stateOutput.
Por qué elegí esto
He mantenido intencionadamente esto como un único « expression » central. No quería ramas internas separadas para el primer ID y el siguiente ID si podía evitarlo. Mantener el cálculo en un solo lugar facilita la reutilización y hace que los cambios futuros sean más seguros. Al final de este disparador, el valor generado se almacena en _stateOutput.
Desencadenador 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 ti. 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 qué hacer a continuación:* añadir la tabla configurada a la aplicación* sustituir este paso de mensaje por un desencadenador «Crear registro »* utilizar _stateOutput como el ID del nuevo registro
Lo he diseñado así porque el comportamiento de la salida puede variar según los casos de uso. Quizás quieras crear un registro inmediatamente. Quizás quieras almacenar primero el valor en otra variable. O quizá quieras utilizarlo en un flujo de trabajo más amplio antes de escribir nada en la tabla. Así que, en lugar de fijar un comportamiento de salida único, mantuve la etapa de salida configurable. Por eso la parte visible para el usuario existe en ambos extremos:* la entrada se puede configurar* la salida se puede configurar* la lógica central permanece en el medio
Por qué los desencadenantes están separados de esta forma
Esta estructura refleja una de las principales decisiones de diseño de todo el bloque lógico: mantener separadas las responsabilidades, pero mantener la lógica centralizada.Por eso no lo incluí todo en un único disparador enorme. Un único disparador gigante podría funcionar, pero resulta más difícil de leer, más difícil de explicar y más difícil de depurar. Por otro lado, dividir los cálculos matemáticos en muchas partes pequeñas también se vuelve confuso. Así que opté por un término medio:* disparadores separados para pasos legibles* un « expression » centralizado para el cálculo real del ID
Esto me ofrece las ventajas de ambas opciones:* la estructura es más fácil de seguir* la lógica es más fácil de mantener* la reutilización es más sencilla* las modificaciones futuras entrañan menos riesgos
Por qué la validación y la protección son importantes en este bloque lógico
Este bloque lógico no solo sirve para generar el siguiente número, sino también para proteger la tabla de datos erróneos. Hay dos puntos principales de protección:
Protección del formato
El bloque lógico comprueba si el último ID existente se ajusta a la estructura esperada antes de continuar. Esto evita que la secuencia continúe basándose en ID que no se ajusten a la estructura esperada.
Protección contra desbordamientos
El bloque lógico comprueba si el siguiente número sigue cabiendo dentro de la longitud numérica permitida. Esto evita que la estructura cambie de forma inesperada a medida que crece la secuencia. Consideré que ambos aspectos eran importantes porque los datos de ID erróneos pueden propagarse rápidamente. Una vez que un patrón incorrecto entra en una tabla y el generador empieza a utilizarlo, ese problema puede afectar a todos los registros futuros. Por eso, el bloque lógico está diseñado para detenerse a tiempo en lugar de continuar en silencio en la dirección equivocada.
Enfoque para la denominación de variables
Quizá hayas observado que los nombres de las variables utilizados dentro del bloque lógico siguen un patrón específico. Ejemplos:_configPrefix_configIdNumberFormat_stateLastId_sequenceNumber
He utilizado intencionadamente un estilo de nomenclatura que se distingue fácilmente de las variables que se crean habitualmente dentro de las aplicaciones. El propósito no es técnico, sino práctico. Cuando importes o copies el bloque lógico a una aplicación, debería quedar inmediatamente claro qué variables pertenecen al bloque lógico y cuáles a la lógica de la aplicación.Las variables del bloque lógico utilizan una estructura coherente, por lo que se agrupan visualmente y es menos probable que entren en conflicto con las variables definidas por el usuario. Esto hace que el bloque lógico sea más fácil de reutilizar y más seguro de integrar en aplicaciones existentes sin conflictos de nombres ni confusión.
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, por lo que el siguiente ID se basa en el último registro real.* Quería que la lógica siguiera siendo fácil de mantener, por lo que el cálculo está centralizado.* Quería que varias entidades en una misma tabla funcionaran correctamente, por lo que cada prefijo tiene su propia secuencia.* Quería proteger la tabla de patrones incorrectos, por lo que las comprobaciones de formato y desbordamiento detienen los datos erróneos antes de la creación del registro.* Quería que el flujo del disparador fuera legible, por lo que las responsabilidades se separan en pasos claros.
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.
Participa
Únete a la comunidad para compartir mejoras, proponer unidades adicionales, informar de problemas o debatir decisiones de arquitectura.
