1. RUTINA PARA CONVERTIR UNA CANTIDAD NUMÉRICA EN
LETRAS.
MOTIVACIÓN.
En algún momento, mientras navegábamos en Internet o mientras analizábamos el código que
“descubrimos” en el trabajo de nuestro amigo Nerdio, nos encontramos con algún
script/función/clase que convertía cadenas numéricas a su equivalente en palabras (en letras,
dirían algunos). Seguramente en ese momento ustedes pensaron lo mismo que yo: “¿para qué me
puede servir eso? Nunca voy a escribir un cheque usando PHP ...”. Y llegados a esa conclusión
simplemente seguimos a otra página web, borramos esa parte del código o la imprimimos en un
papel que nunca más encontramos.
Y entonces llega el día en que el jefe abre la boca y salen estas palabras: “Quiero que los cheques
(facturas, recibos, etc.) las haga el sistema” (Así le dicen siempre, “el sistema”). Claro que es
más fácil ponerlo a uno a codificar que decirle a la secre que se salga de facebook y escriba un
cheque en la bendita máquina de escribir. Y es entonces cuando buscamos, rebuscamos y no
encontramos la dichosa rutina.
Así que llegamos al problema: ¿y ahora cómo diablos hago para que mi número salga convertido
en letras?
Se encuentran algunas funciones para eso en Internet (personalmente probé varias) pero como
sucede con todo código que no es nuestro, si queremos modificar algo primero tenemos que
entender cómo funciona y lo más probable es que pasemos varias horas sin entender nada. Y a
menos que esté bien que nuestros cheques digan algo así como “двести пятьдесят рублей”,
tenemos que modificar algo.
Así que me dediqué a elaborar mi propia rutina y, siendo de buen corazón, ahora quiero
compartirla con ustedes, pero no solamente quiero entregarles el código sino también el
razonamiento detrás del código. No pretendo que sea la rutina mejor pensada ni la mejor
codificada. Estoy seguro de que hay margen para perfeccionarla y al menos cinco formas de
hacerla mejor, pero, parafraseando el dicho: “no hay código por malo que sea que no nos deje
algo bueno”. Como siempre, los comentarios son bien recibidos.
PARA LOS IMPACIENTES
¿No te interesa nada de lo que tengo que decir? Ve directamente a la sección EL SCRIPT,
COMPLETO, allí está todo el código.
Adaptaciones que hay que hacer: en la función num2letras() hay que cambiar el nombre de la
moneda (la variable $moneda) y arreglar el plural al final de la función. También hay que tener
en cuenta que pueden introducirse comillas tipográficas (“”) al hacer copy/paste.
¿La forma de uso?: ver la sección “¿Y CÓMO USO TODO ESO?”.
Mis disculpas por hacerte trabajar.
ANALISIS DEL SCRIPT
ALGUNOS DATOS PREVIOS
1. Este script maneja números de hasta 15 cifras (más dos decimales), o sea, llega hasta 999
billones y centavos. Esto es así debido a problemas de precisión que surgen al trabajar
con números grandes.
2. En mi país, Guatemala, utilizamos como moneda el Quetzal. Decidí poner el nombre de la
moneda en una variable al principio del código porque me imagino que, si estás leyendo
esto, es bastante probable que tengas que cambiarlo (a menos que seas de Guatemala,
2. claro). La forma del plural se arregla al final del script, cuando ya sabemos exactamente
con qué cantidad estamos tratando.
3. Utilizamos dos funciones: enletras() y num2letras(). La primera convierte un número de
tres cifras en su equivalente en palabras. La segunda función se apoya en la primera:
separa un número de hasta 15 dígitos en grupos de tres cifras y pasa cada uno de esos
grupos a la primera función para su procesamiento. Por último, esta segunda función da
formato a la cadena de palabras obtenida para su presentación correcta.
LA FUNCIÓN ENLETRAS()
La función enletras() toma un solo parámetro: una cadena de caracteres 1 que representa un
número de hasta tres cifras y que se pasa desde la función num2letras() y devuelve ese número
convertido en palabras:
function enletras($valor) {
-- código php
return $en_letras;
}
Básicamente, en español2, podemos “decir” cualquier número usando grupos de tres dígitos y
concatenando esos grupos con calificadores del nivel. Así por ejemplo, el número 11,234,567 será
“once millones doscientos treinta y cuatro mil quinientos sesenta y siete”, donde los calificadores
son las palabras “millones” y “mil”, que concatenan los tres grupos “once”, “doscientos treinta y
cuatro” y “quinientos sesenta y siete”.
Teniendo lo anterior en cuenta, podemos crear tres matrices (una para las unidades, otra para las
decenas y una tercera para las centenas) que nos permiten formar el nombre de cualquier grupo
de tres dígitos. Hay que tener en cuenta que los números 11 al 19 tienen su propio nombre,
mientras que los otros números utilizan una combinación de nombres más sencillos (por ejemplo,
43 = “cuarenta y tres”). Las matrices serán:
$uni[0] = "cero";
$uni[1] = "un";
$uni[2] = "dos";
$uni[3] = "tres";
$uni[4] = "cuatro";
$uni[5] = "cinco";
$uni[6] = "seis";
$uni[7] = "siete";
$uni[8] = "ocho";
$uni[9] = "nueve";
$uni[11] = "once";
$uni[12] = "doce";
$uni[13] = "trece";
$uni[14] = "catorce";
$uni[15] = "quince";
$uni[16] = "dieciseis";
$uni[17] = "diecisiete";
$uni[18] = "dieciocho";
$uni[19] = "diecinueve";
$dec[2] = "veinti";
$dec[3] = "treinta y ";
1 No es lo mismo 'una cadena de caracteres que representa un número' que 'un número'. Ver la
documentación de PHP: http://www.php.net/manual/es/language.types.php
2 Castellano, para los puristas.
3. $dec[4] = "cuarenta y ";
$dec[5] = "cincuenta y ";
$dec[6] = "sesenta y ";
$dec[7] = "setenta y ";
$dec[8] = "ochenta y ";
$dec[9] = "noventa y ";
$dec[10] = "diez";
$dec[20] = "veinte";
$dec[30] = "treinta";
$dec[40] = 'cuarenta';
$dec[50] = 'cincuenta';
$dec[60] = 'sesenta';
$dec[70] = 'setenta';
$dec[80] = 'ochenta';
$dec[90] = 'noventa';
$cen[1] = "ciento ";
$cen[2] = "doscientos ";
$cen[3] = "trescientos ";
$cen[4] = "cuatrocientos ";
$cen[5] = "quinientos ";
$cen[6] = "seiscientos ";
$cen[7] = "setecientos ";
$cen[8] = "ochocientos ";
$cen[9] = "novecientos ";
$cen[100] = "cien";
$cen[200] = "doscientos";
$cen[300] = "trescientos";
$cen[400] = "cuatrocientos";
$cen[500] = "quinientos";
$cen[600] = "seiscientos";
$cen[700] = "setecientos";
$cen[800] = "ochocientos";
$cen[900] = "novecientos";
Como primer paso, determinamos el largo de la cadena que la función recibió como parámetro.
Este largo nos dice cuántos dígitos tiene el grupo y, según sea el valor obtenido, así lo
procesaremos.
$largo = strlen($valor);
La variable $largo puede tomar solamente tres valores: 1, 2 ó 3. Debemos recordar que una
cadena de caracteres también es una matriz y cada uno de sus elementos puede identificarse con
un índice. Por ejemplo, para la cadena $var = 'ABC', sus elementos serán $var[0] = 'A', $var[1] =
'B' y $var[2] = 'C'. De la misma manera, podemos usar $valor[0], $valor[1] y $valor[2] para
identificar cada uno de los dígitos del parámetro $valor que se ha pasado a la función.
Usaremos un lazo switch() para decidir cómo procesaremos el parámetro $valor.
switch($largo) {
case 1:
$en_letras = $uni[$valor[0]];
break;
El primer caso es el más sencillo. Hemos recibido una cadena que contiene un solo caracter: un
dígito cuyo valor se encuentra entre 1 y 9. Para este caso, simplemente buscamos el valor
correspondiente en la matriz $uni[] y lo asignamos a la variable $en_letras.
case 2:
4. $decena = $valor[0] . $valor[1];
if($valor[1] == 0) {
// decena exacta
$en_letras = $dec[$decena];
} else {
if($valor[0] > 1) {
// entre 21 y 99
$en_letras = $dec[$valor[0]] . $uni[$valor[1]];
} else {
// entre 11 y 19
$en_letras = $uni[$decena];
}
}
break;
El segundo caso trata con cadenas de dos caracteres: un número de dos dígitos cuyo valor se
encuentra entre 11 y 99. Aquí pueden darse dos situaciones diferentes: que tengamos una
decena exacta (10, 20, 30, etc.) o que no lo sea.
Si el valor del último dígito ($valor[1]) es cero, la decena será exacta y buscamos su “nombre” en
la matriz $dec[]. Para ello usamos la variable $decena, formada por los dígitos $valor[0] y
$valor[1].
Si no se trata de una decena exacta (por ejemplo: 34, 57, 89, etc.), aún tenemos dos opciones que
considerar: que se trate de números entre 11 y 19, en cuyo caso buscamos su nombre en la
matriz $uni[], o que se trate de un número mayor. En este último caso concatenamos el nombre
de la decena obtenido de la matriz $dec[] con el nombre de la unidad obtenido de la matriz
$uni[].
case 3:
// centenas, decenas y unidades
$centena = $valor[0] . $valor[1] . $valor[2];
if($centena != '000') {
$decena = $valor[1] . $valor[2];
if($valor[1] == 0 AND $valor[2] == 0) {
// centena exacta
$en_letras = $cen[$centena];
} else {
if($valor[2] == 0) {
// decena exacta
$en_letras = $dec[$decena];
} else {
if($valor[1] == 0 AND $valor[2] == 1) {
// exactamente 1
$en_letras = $uni[$valor[2]];
} elseif($valor[1] > 1) {
// entre 21 y 99
$en_letras = $dec[$valor[1]] . $uni[$valor[2]];
} else {
// entre 11 y 19
$en_letras = $uni[$decena];
}
}
$en_letras = $cen[$valor[0]] . $en_letras;
}
} else {
$en_letras = "000";
}
break;
}
5. Veamos ahora el último caso: una cadena que representa un número de tres dígitos (entre 100 y
999).
Comprobamos que la cadena no sea igual a '000' (por ejemplo, si tenemos originalmente un
número como 1,000 y estamos procesando el grupo de la derecha). Si la cadena es '000',
simplemente devolvemos esa misma cadena para manejarla en la función num2letras(), como se
verá más adelante.
Ahora veamos las decenas. Si los dígitos $valor[1] y $valor[2] son iguales a cero, tenemos una
centena exacta (100, 200, 300, etc.). En este caso buscamos el nombre directamente en la matriz
$cen[].
Como en el caso anterior del switch(), revisamos si estamos tratando con una decena exacta. Si
es así, obtenemos su nombre directamente de la matriz $dec[].
También debemos comprobar otra situación especial: el segundo dígito es cero y el tercero es '1'
(101, 201, 301, etc.) En este caso asignamos temporalmente a la variable $en_letras el valor
'UN'. Y decimos que es temporalmente porque un poco más abajo se decide si conservamos este
valor o no.
Por último y como en el segundo caso del switch() (la cadena de dos números), si los últimos dos
dígitos se encuentran en el rango entre 11 y 99, buscamos su nombre en la matriz $uni[]. Si se
encuentran entre 21 y 99 (el número 20 ya se trato antes como una decena exacta), su nombre
será la combinación de el nombre de la decena en la matriz $dec[] y la unidad en la matriz $uni[].
Ahora obtenemos el nombre de la centena de la matriz $cen[] y le agregamos el valor de la
variable $en_letras (que contiene el nombre de los dos dígitos restantes).
Una vez que llegamos aquí, la variable $en_letras contiene el “nombre” del grupo de tres dígitos
que se pasó como parámetro y solo nos queda devolverlo.
return $en_letras;
LA FUNCIÓN NUM2LETRAS()
Esta función se ocupa básicamente de darle un formato adecuado para su impresión al valor
devuelto por la función $enletras() .Toma como parámetro un número que será el que queremos
convertir de dígitos a “palabras”.
function num2letras($val) {
-- código php
}
Definimos una variable que contendrá el nombre de la moneda. Uso una variable porque es más
sencillo de adaptar. En mi país, Guatemala, usamos el Quetzal como moneda.
$moneda = 'QUETZAL';
Como el número que se pasará a esta función puede venir “contaminado” con espacios, comas,
símbolos de moneda, etc., debemos limpiarlo para quedarnos solamente con dígitos y, tal vez, un
punto decimal. La función eregi_replace() toma como patrón las letras A-Z (mayúsculas o
minúsculas) y el símbolo '$'. Eso cubre casi todos los símbolos de monedas de los países
hispanoparlantes, pero si estas en un país que usa otro símbolo (por ejemplo, el yen), éste se
agrega aquí.
$num = eregi_replace("[A-Za-z$, ]", "", $val);
Hay que convertir nuestro número a número. No, todavía no estoy desvariando. Suena confuso,
pero hasta ahora hemos trabajado con cadenas de caracteres que representan números, no con
números verdaderos. Sin embargo, necesitamos que nuestro número lo sea realmente, para darle
formato con dos decimales y los miles separados por comas. ¿Para qué ponerle comas después de
que se las quitamos? La importancia de esto se verá más abajo.
6. $num = (float) $num;
Como dijimos, necesitamos un número. Además, al convertir nuestra cadena usando float(), nos
aseguramos de desaparecer los ceros a la izquierda.
$num = number_format($num, 2, ".", ",");
Si anteriormente no no podíamos estar seguros de cómo estaba formado nuestro número, en este
momento ya tenemos con seguridad un número con un punto y dos decimales. Lo separamos en
dos partes usando el punto decimal como referencia. Por ahora solo manejaremos las cantidades
enteras y nos ocuparemos de los decimales (los centavos) al final del script.
$enteros = substr($num, 0, -3);
Una vez que sabemos cuál es el valor de la parte entera, lo descomponemos en grupos de tres
dígitos usando las comas que introdujimos anteriormente (les dije que eran importantes). Si
nuestro número originalmente traía comas, no podíamos confiar en que estaban en el lugar
correcto, así que lo que hicimos fue asegurarnos de que el número tuviera el formato
exactamente como lo necesitamos.
$trios = explode(",", $enteros);
Ahora algunas inicializaciones necesarias: creamos la matriz $partes() que contendrá como
elementos los “nombres” que vamos obteniendo de la función enletras(); un contador $n = 0, que
será el índice de cada elemento que se agregue a esa matriz; y una variable vacía, $en_letras, en
la que se formará el “nombre” de nuestro número.
$partes = array();
$n = 0;
$en_letras = '';
Ahora recorremos cada grupo de números. Como dijimos al principio, por asuntos relacionados
con la precisión, solamente manejaremos hasta 15 dígitos. Si cada grupo contiene tres dígitos (o
menos), debemos hacer el siguiente procedimiento un máximo de cinco veces.
for($i = 5; $i >= 0; $i--) {
Primero comprobamos la longitud del grupo, para saber si en realidad existe. Asumimos que
tenemos 15 dígitos, pero esto puede no ser verdad.
$largo = strlen($trios[$i]);
Si el grupo existe ($largo tiene un valor), vamos a procesar el grupo. Más arriba habíamos
dividido el número en grupos (almacenados en la matriz $trios[]). Como nuestro lazo inicia con $i
= 5 y va disminuyendo, quiere decir que el primer grupo que enviaremos a la función enletras()
será $trios[5], que es el trío de la extrema derecha (el de menor nivel) y a partir de allí nos
moveremos hacia la izquierda hasta llegar al trío de la extrema izquierda, el de mayor nivel.
La función enletras() procesa cada trío y, como ya vimos antes, devuelve su valor expresado en
palabras. Ese valor se agrega como un nuevo elemento (iniciando con el índice $n = 0 y
aumentando de valor) a la matriz $partes[]. Ese proceso se repite para cada iteración del lazo
for().
if($largo) {
$partes[$n] = enletras($trios[$i]);
En cada iteración también analizamos el contenido del elemento $partes[$n] que acabamos de
agregar.
7. Veamos el primer elemento (número <= 999). Como ya dijimos antes, si se da el caso de que el
grupo este formado solamente por ceros (por ejemplo, el grupo de la derecha en el número
1,000), la función devuelve esa misma cadena ('000') y la variable $en_letras toma el valor “DE“.
Si el número no es 000, $en_letras toma el valor contenido en el elemento $partes[0].
if($partes[0] != '000') {
$en_letras = $partes[0];
} else {
$en_letras = ' DE ';
}
Analicemos el segundo grupo desde la derecha, el de los miles. Si está vacío (nuestro número
solo tenía tres dígitos), simplemente continuamos hasta el final del lazo para otra iteración. Si el
valor de la variable $en_letras es solamente “DE“, vaciamos esa variable (por ejemplo, si nuestro
número es 1,000 no se dice “un mil de quetzales”, sino “un mil quetzales”. La palabra “de”
sobra). Si $en_letras tiene otro valor (por ejemplo: “trescientos setenta y uno”), concatenamos el
valor devuelto por la función enletras() para este trío con el ya existente en la variable
$en_letras, para formar un nombre que contiene miles, por ejemplo: “dos mil trescientos setenta
y uno”.
if($partes[1] != '') {
if($en_letras == ' DE ') {
$en_letras = '';
}
$en_letras = $partes[1] . " MIL " . $en_letras;
}
Para el tercer grupo desde la derecha, el de los millones, el procedimiento es similar. Si existe, lo
procesamos, si no, continuamos hasta el final del lazo. Como nuestro calificador puede ser
“millón” o “millones”, primero decidimos cuál usaremos según sea el valor contenido en
$partes[2]. Si $partes[1] y $partes[0] (los dos grupos de la derecha) son diferentes de '000',
asignamos a la variable $en_letras todos esos valores (por ejemplo: cinco millones dos mil
trescientos setenta y uno). En el caso de que $partes[1] y $partes[0] sean ambas iguales a '000'
(por ejemplo: 1,000,000), la concatenación será “un millón de”.
if($partes[2] != '') {
$millones = " MILLONES ";
if(strtoupper($partes[2]) == 'UN') {
$millones = " MILLON ";
}
if($partes[0] != '000' AND $partes[1] != '000') {
$en_letras = $partes[2] . $millones . $en_letras;
} else {
$en_letras = $en_letras = $partes[2] . $millones . ' DE ';
}
}
El cuarto grupo (miles de millones) sigue un análisis similar. Si este grupo existe y los otros
grupos no contienen únicamente ceros, concatenamos esos valores. Siguiendo nuestro ejemplo,
$en_letras contiene en este momento el valor “cinco millones dos mil trescientos setenta y uno” y
luego de procesar este grupo contendrá “ochenta mil cinco millones dos mil trescientos setenta y
uno”. Si los otros grupos todos contienen '000' (1,000,000,000), $en_letras será igual a “un mil
millones de”.
if($partes[3] != '') {
if($partes[0] != '000' AND $partes[1] != '000' AND $partes[2] != '000') {
$en_letras = $partes[3] . " MIL " . $en_letras;
} else {
8. $en_letras = $partes[3] . " MIL MILLONES DE ";
}
}
Finalmente, el quinto grupo, el de la extrema izquierda. El análisis es también parecido. Primero
decidimos qué calificador utilizaremos: “billón” o “billones”. Luego, si los restantes grupos tienen
un valor, concatenamos esos valores y los asignamos a la variable $en_letras. En caso contrario
(todos los otros grupos contienen '000'), nuestro nombre será “1 billón de”.
if($partes[4] != '') {
$billones = " BILLONES ";
if(strtoupper($partes[4]) == 'UN') {
$billones = " BILLON ";
}
if($partes[0] != '000' AND $partes[1] != '000' AND $partes[2] != '000' AND
$partes[3] != '000') {
$en_letras = $partes[4] . $billones . $en_letras;
} else {
$en_letras = $partes[4] . $billones . ' DE ';
}
}
$n++;
}
}
Ahora debemos ocuparnos del plural de la moneda. Como ya indiqué, en mi país la moneda
nacional es el Quetzal. Plural: quetzales (se agrega 'es'). La regla es sencilla: si el nombre de la
moneda termina en vocal, se agrega una 's' (peso → pesos, lempira → lempiras, etc.) en otro caso,
se agrega 'es' (quetzal → quetzales, bolivar → bolívares, etc.)
if(strtoupper($en_letras) != 'UN') {
$moneda = " " . $moneda . "ES";
}
Vamos a ocuparnos de los centavos. La presentación que usamos aquí es 'XX/100', donde XX
representa la cantidad en centavos (por ejemplo: 35/100). También podría usarse 'XX centavos' o,
en el caso de que sean cero centavos decir 'tantos quetzales exactos'. Eso queda al gusto del
contador de la empresa o de la persona a cargo de los cheques/recibos/facturas o lo que sea. En
cualquier caso, la siguiente parte del script puede modificarse fácilmente.
$centavos = substr($num, -2) . "/100";
Por último, lo ponemos todo junto y devolvemos nuestro resultado.
$letras = strtoupper($en_letras) . $moneda . " CON " . $centavos;
return $letras;
¿Y CÓMO USO TODO ESO?
En la práctica, en este momento ya debemos tener una cantidad disponible. Ya sea porque la
calculamos, porque viene de un formulario mediante $_POST[], estaba almacenada en una tabla
de nuestra base de datos o simplemente existe en alguna forma. Cuando llega el momento de
imprimir esa cantidad, simplemente hacemos la llamada:
$echo num2letras($total_en_numero);
9. CONCLUSIONES
Como dije antes, seguramente hay una forma mejor y más eficiente de codificar todo esto.
Pueden modificar este script a su gusto, no hay problema con eso. Si han aprendido algo al leer
este trabajo, me doy por satisfecho. Sus comentarios, críticas y sugerencias son siempre
bienvenidos, sobre todo si se refiere a errores de código o tipográficos.
Y, como siempre, recuerden que usan este script por su cuenta y riesgo. Funciona como debe de
funcionar y hace lo que dice que hace (y nada más), pero si algo les pasa (se les calienta la
cerveza, los deja la novia/el novio, les da gripe, los despiden del trabajo, reprueban el exámen,
etc.), no me culpen a mí.
Juan Carlos Vásquez
jcvasquez07@gmail.com
EL SCRIPT, COMPLETO
Recuerden:
– Hay que adaptar la variable $moneda y el plural al final de la función num2letras().
– Al hacer copy/paste pueden introducirse comillas tipográficas (“”). Hay que sustituirlas
por comillas normales en su editor preferido.
function enletras($parte) {
// toma como parámetro un numero entero de hasta tres cifras
$uni[0] = "cero";
$uni[1] = "un";
$uni[2] = "dos";
$uni[3] = "tres";
$uni[4] = "cuatro";
$uni[5] = "cinco";
$uni[6] = "seis";
$uni[7] = "siete";
$uni[8] = "ocho";
$uni[9] = "nueve";
$uni[11] = "once";
$uni[12] = "doce";
$uni[13] = "trece";
$uni[14] = "catorce";
$uni[15] = "quince";
$uni[16] = "dieciseis";
$uni[17] = "diecisiete";
$uni[18] = "dieciocho";
$uni[19] = "diecinueve";
$dec[2] = "veinti";
$dec[3] = "treinta y ";
$dec[4] = "cuarenta y ";
$dec[5] = "cincuenta y ";
$dec[6] = "sesenta y ";
$dec[7] = "setenta y ";
$dec[8] = "ochenta y ";
$dec[9] = "noventa y ";
$dec[10] = "diez";
$dec[20] = "veinte";
$dec[30] = "treinta";
$dec[40] = 'cuarenta';
$dec[50] = 'cincuenta';
$dec[60] = 'sesenta';