martes, 16 de febrero de 2021

Convertir un número a letras con una sola fórmula

Una cuestión planteada en multitud de ocasiones es la forma de convertir un número a letras; lo que se puede (y siempre se ha podido) hacer bien empleando algo de programación en VBA (ver ejemplo aquí) o bien con fórmulas dispuestas a lo largo de diferentes celdas con 'retorcidas' búsquedas y anidaciones...

Hoy veremos como con una única fórmula, en una sola celda, podemos realizar la conversión deseada: transformar un número a letras.
La idea surgió al ver a algunos compañeros del programa Microsoft MVP (Sergei Baklan, Frédéric LE GUEN y algún otro que aportó su granito) elaborar una fórmula para dicha conversión a letras en inglés y francés (puedes ver la publicación de Frédéric LE GUEN aquí).

El problema es que a la hora de leer los número en los diferentes idiomas el algoritmo de lectura es muy diferente, i.e., el patrón en inglés es más homogeneo que en español, de forma similar si comparamos francés y español... por suerte o desgracia los números leídos en castellano tienen bastante más casuística que otras lenguas :'(

Esto me hizo repensar la fórmula propuesta por estos gurús de Excel y generar la fórmula para los números en español.
Y basándome en el algoritmo descrito en este artículo, y haciendo uso de la función LET (de uso para suscriptores de Microsoft 365 y parece que a fecha de hoy también en Excel 2019 y 2019. Leer más aquí) construí la fórmula, capaz de transformar a letras números de 0 hasta 999.999.999

Anticipo que la fórmula, no puede ser de otra forma, es larga y se anida una función LET dentro de otra... empleando además matrices de constantes, y por supuesto el concepto de matrices.

Disponemos en la celda A1 del número que deseamos transformar a letras.
Y en B1 añadimos la fórmula buscada (que pasaré a explicar posteriormente):
=LET(importe;A1;
QMillones;SI(importe>999999;ENTERO(importe/1000000));
QMiles;SI(importe>999;ENTERO(importe/1000)-QMillones*1000);
QCentenas;importe-ENTERO(importe/1000)*1000;
QEntera;ENTERO(importe);
QDecimal;REDONDEAR((importe-ENTERO(importe))*100;0);

centfin;SI(QDecimal=1;" céntimo";" céntimos");
eurfin;SI(QEntera=1;" euro";" euros");

matriz;ELEGIR({1;2;3;4};QMillones;QMiles;QCentenas;QDecimal);
calculo;LET(
centena2;ENTERO(matriz/100);
decena2;ENTERO((matriz-centena2*100)/10);
unidad2;ENTERO((matriz-centena2*100-decena2*10));

centenas;{""\"cien"\"doscientos"\"trescientos"\"cuatrocientos"\"quinientos"\"seiscientos"\"setecientos"\"ochocientos"\"novecientos"};
decenas;{""\" diez"\" veinte"\" treinta"\" cuarenta"\" cincuenta"\" sesenta"\" setenta"\" ochenta"\" noventa"};
unidades;{""\" un"\" dos"\" tres"\" cuatro"\" cinco"\" seis"\" siete"\" ocho"\" nueve"};
dieces;{"diez"\"once"\"doce"\"trece"\"catorce"\"quince"\"dieciseis"\"diecisiete"\"dieciocho"\"diecinueve"};
veintes;{"veinte"\"veintiuno"\"veintidos"\"veintitres"\"veinticuatro"\"veinticinco"\"veintiseis"\"veintisiete"\"veintiocho"\"veintinueve"};
tatantos;{""\" y un"\" y dos"\" y tres"\" y cuatro"\" y cinco"\" y seis"\" y siete"\" y ocho"\" y nueve"};

num_letra1;INDICE(centenas;1;centena2+1);
num_letra2;SI(centena2=1;num_letra1&"to";num_letra1);
num_letra3;num_letra2&INDICE(decenas;1;decena2+1);
num_letra4;SI(decena2=0;INDICE(unidades;1;unidad2+1);"");
num_letra5;SI(decena2=1;EXTRAE(num_letra3;1;LARGO(num_letra3)-4)&INDICE(dieces;1;unidad2+1);"");
num_letra6;SI(decena2=2;EXTRAE(num_letra3;1;LARGO(num_letra3)-6)&INDICE(veintes;1;unidad2+1);"");
num_letra7;SI(decena2>2;INDICE(tatantos;1;unidad2+1);"");
num_letra8;SI(decena2=1;num_letra5;SI(decena2=2;num_letra6;num_letra3&num_letra4&num_letra7));
SI(matriz=100;"cien";num_letra8));

txtMillones;SI(importe>999999;SI(QMillones=1;"un millón ";INDICE(calculo;1)&" millones ");"");
txtMil;SI(importe>999;SI(QMiles=1;"mil ";SI(QMiles=0;"";INDICE(calculo;2)&" mil "));"");
txtCent;SI(QEntera=0;"";SI(INDICE(calculo;3)="";"";INDICE(calculo;3))&eurfin);
txtDecimal;SI(INDICE(calculo;4)="";"";SI(QEntera=0;"";" con ")&INDICE(calculo;4)&centfin);

SI(importe=0;"cero euros";ESPACIOS(txtMillones&txtMil&txtCent&txtDecimal)))


¿Larga, verdad?... Normal por la cantidad de casuística a evaluar...
Convertir un número a letras con una sola fórmula
Para descomponer y explicar la fórmula empezaríamos por el LET anidado:
LET(
centena2;ENTERO(matriz/100);
decena2;ENTERO((matriz-centena2*100)/10);
unidad2;ENTERO((matriz-centena2*100-decena2*10));

centenas;{""\"cien"\"doscientos"\"trescientos"\"cuatrocientos"\"quinientos"\"seiscientos"\"setecientos"\"ochocientos"\"novecientos"};
decenas;{""\" diez"\" veinte"\" treinta"\" cuarenta"\" cincuenta"\" sesenta"\" setenta"\" ochenta"\" noventa"};
unidades;{""\" un"\" dos"\" tres"\" cuatro"\" cinco"\" seis"\" siete"\" ocho"\" nueve"};
dieces;{"diez"\"once"\"doce"\"trece"\"catorce"\"quince"\"dieciseis"\"diecisiete"\"dieciocho"\"diecinueve"};
veintes;{"veinte"\"veintiuno"\"veintidos"\"veintitres"\"veinticuatro"\"veinticinco"\"veintiseis"\"veintisiete"\"veintiocho"\"veintinueve"};
tatantos;{""\" y un"\" y dos"\" y tres"\" y cuatro"\" y cinco"\" y seis"\" y siete"\" y ocho"\" y nueve"};

num_letra1;INDICE(centenas;1;centena2+1);
num_letra2;SI(centena2=1;num_letra1&"to";num_letra1);
num_letra3;num_letra2&INDICE(decenas;1;decena2+1);
num_letra4;SI(decena2=0;INDICE(unidades;1;unidad2+1);"");
num_letra5;SI(decena2=1;EXTRAE(num_letra3;1;LARGO(num_letra3)-4)&INDICE(dieces;1;unidad2+1);"");
num_letra6;SI(decena2=2;EXTRAE(num_letra3;1;LARGO(num_letra3)-6)&INDICE(veintes;1;unidad2+1);"");
num_letra7;SI(decena2>2;INDICE(tatantos;1;unidad2+1);"");
num_letra8;SI(decena2=1;num_letra5;SI(decena2=2;num_letra6;num_letra3&num_letra4&num_letra7));
SI(matriz=100;"cien";num_letra8))


Este primer LET realiza la conversión de número a letra para valores entre 0 y 999, que nos servirá para transformar decimales, centenas, miles o millones.
Es decir, la idea general es descompone el importe inicial en los tramos normales, y pasarlo por el proceso de esta función LET, donde obtenemos el valor de las posiciones de U, D y C (unidades, decenas y centenas)... extrapolables al resto de tramos de cualquier número (miles, millones, etc...)
Muy importantes son las matrices de constantes descritas; en mi caso he preferido desarrollarlas horizontálmente, i.e., por columnas, pero igualmente podrían haberse implantado verticalmente, por filas. Basta cambiar la barra invertida \ por punto y coma ;
centenas;{""\"cien"\"doscientos"\"trescientos"\"cuatrocientos"\"quinientos"\"seiscientos"\"setecientos"\"ochocientos"\"novecientos"};
decenas;{""\" diez"\" veinte"\" treinta"\" cuarenta"\" cincuenta"\" sesenta"\" setenta"\" ochenta"\" noventa"};
unidades;{""\" un"\" dos"\" tres"\" cuatro"\" cinco"\" seis"\" siete"\" ocho"\" nueve"};
dieces;{"diez"\"once"\"doce"\"trece"\"catorce"\"quince"\"dieciseis"\"diecisiete"\"dieciocho"\"diecinueve"};
veintes;{"veinte"\"veintiuno"\"veintidos"\"veintitres"\"veinticuatro"\"veinticinco"\"veintiseis"\"veintisiete"\"veintiocho"\"veintinueve"};
tatantos;{""\" y un"\" y dos"\" y tres"\" y cuatro"\" y cinco"\" y seis"\" y siete"\" y ocho"\" y nueve"};

Estas matrices recogen los casos generales y especiales de los número leídos en castellano.

Sobre estas matrices, o a partir de ellas, comenzamos un proceso (basado en la programación en VBA del artículo comentado más arriba), que nos permite identificar qué textos corresponden a cada número:
num_letra1;INDICE(centenas;1;centena2+1);
num_letra2;SI(centena2=1;num_letra1&"to";num_letra1);
num_letra3;num_letra2&INDICE(decenas;1;decena2+1);
num_letra4;SI(decena2=0;INDICE(unidades;1;unidad2+1);"");
num_letra5;SI(decena2=1;EXTRAE(num_letra3;1;LARGO(num_letra3)-4)&INDICE(dieces;1;unidad2+1);"");
num_letra6;SI(decena2=2;EXTRAE(num_letra3;1;LARGO(num_letra3)-6)&INDICE(veintes;1;unidad2+1);"");
num_letra7;SI(decena2>2;INDICE(tatantos;1;unidad2+1);"");
num_letra8;SI(decena2=1;num_letra5;SI(decena2=2;num_letra6;num_letra3&num_letra4&num_letra7));
SI(matriz=100;"cien";num_letra8))

Si formulamos estos cálculos en distintas celdas veríamos el proceso (seguro mejorable!!):
Convertir un número a letras con una sola fórmula
Este proceso de cálculo va concatenando, según cumplimos las condiciones o casuísticas, los distintos componentes o partes del número en letras... ¡¡de cualquier número entre 0 y 999!!!; que sirve para tramos de centenas, miles, millones y por supuesto parte decimal ;-)

Con este complejo proceso construido, solo nos queda ir pasándole las partes del importe de la celda A1. Por ejemplo, si tenemos el número
123.056.709,03
Nuestra función LET principal comienza separando las partes:
=LET(importe;A1;
QMillones;SI(importe>999999;ENTERO(importe/1000000));
QMiles;SI(importe>999;ENTERO(importe/1000)-QMillones*1000);
QCentenas;importe-ENTERO(importe/1000)*1000;
QEntera;ENTERO(importe);
QDecimal;REDONDEAR((importe-ENTERO(importe))*100;0);

centfin;SI(QDecimal=1;" céntimo";" céntimos");
eurfin;SI(QEntera=1;" euro";" euros");
Podemos ver esos cálculos, si fuera necesario para la explicación, sobre las celdas de nuestra hoja:
Convertir un número a letras con una sola fórmula

Aprovechando la ocasión para definir los sufijos o coletillas para la parte entera y decimal del número: euro/s y céntimo/s

Como paso previo a procesar y convertir cada tramo del importe de la celda A1 generamos una matriz con los importes obtenidos de cada parte (millones, miles, centenas o decimales):
matriz;ELEGIR({1;2;3;4};QMillones;QMiles;QCentenas;QDecimal);
La función ELEGIR es la encargada de crear esa matriz de elementos variables...

Nuestra fórmula pasa cada elemento de esta 'matriz' por el proceso de conversión a letras, de acuerdo a lo anteriormente definido (con el LET anidado)... para finalmente, una vez 'convertido a letras' cada parte por separado... unificarlo y concatenarlo con los prefijos, sufijos, separadores y concatenadores correspondientes.
Lo que vemos en la última parte de nuestra fórmula:
txtMillones;SI(importe>999999;SI(QMillones=1;"un millón ";INDICE(calculo;1)&" millones ");"");
txtMil;SI(importe>999;SI(QMiles=1;"mil ";SI(QMiles=0;"";INDICE(calculo;2)&" mil "));"");
txtCent;SI(QEntera=0;"";SI(INDICE(calculo;3)="";"";INDICE(calculo;3))&eurfin);
txtDecimal;SI(INDICE(calculo;4)="";"";SI(QEntera=0;"";" con ")&INDICE(calculo;4)&centfin);

SI(importe=0;"cero euros";ESPACIOS(txtMillones&txtMil&txtCent&txtDecimal))

Donde con la función INDICE recuperamos cada elemento de nuestra 'matriz' tratada y convertida a letras.
Los condicionales aplicados en cada caso terminan discriminando las especificaciones concretas para los casos 'especiales' del castellano.
El último condicional se encarga de salvar el último caso 'raro' y de concatenar y unir las partes finales obtenidas... llegando al resultado esperado!!

Lo más importante de este ejercicio es que se nos permite, como usuarios 'normales' (sin conocimientos de programación en VBA), con funciones y fórmulas habituales (SI, INDICE, ELEGIR, ENTERO, ...), replicar algoritmos y procesos de cálculos complejos...
Basta tener claridad mental de cuál es nuestro objetivo, y conocer todas las opciones, caminos y variables con las que nos toparemos...
Espero disfrutes este artículo tanto como yo lo he hecho.

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.