Curso Básico de Programación
en Visual Basic

Entrega Dieciséis: 6/Abr/98
por Guillermo "guille" Som

Si quieres linkar con otras entregas, desde el índice lo puedes hacer

 

Habrás notado que ya no aparecen los links al principio de esta entrega, ello se debe a que he añadido una nueva página, desde la cual puedes linkar con todas las entregas, además de poder ver una lista de las "palabras" tratadas en todas las entregas aparecidas, así como en que entrega(s) apareció esa palabra, no es nada "preciso", pero intentaré ir "depurándolas".
La intención es que puedas saber de un vistazo en que entrega se ha tratado y... espero hacerlo pronto, en cual de ellas es en la que se ha explicado, porque una cosa es que se use la palabra y otra diferente que se explique...

Como digo, es sólo un primer intento, que espero ir mejorando, para que te sea fácil encontrar la palabra o tema que te interesa.

Decirte que esa lista la he "sacado" con una utilidad, que seguramente pondré pronto en mis páginas y que trata de mostrar cada una de las "palabras" que aparecen en los ficheros que se procesen... además de esa utilidad, he tenido que ir leyéndome cada una de las palabras y tomando nota, por lo tanto alguna se me ha podido escapar...

Bueno, ya vale... vamos al tema de la presente entrega:

Los ficheros aleatorios (Random)

Pues eso, ahora le toca el turno a los ficheros aleatorios. Ya te comenté que este tipo de fichero se suele usar cuando todos los datos que incluye tienen la misma longitud. Normalmente a cada uno de los datos guardados se les llama registro. Todos los registros tienen la misma longitud, o al menos deberían tenerla... no, no es una contradicción, pero si quieres que funcione bien, deben ser iguales, porque el sistema que usa el VB para acceder a cada uno de los registros es una simple operación:
Posición_Actual = (Número_Registro -1) * Tamaño_del_Registro + 1
Pero no te asustes... tú no tienes que hacer ningún cálculo... de eso se encarga el Visual Basic.

Para acceder a los datos de los ficheros abiertos como Random, vienen como anillo al dedo, (si es que el anillo te encaja bien), los tipos definidos por el usuario, aunque cualquier cadena serviría para esta tarea.

Antes de empezar a manejar ficheros de acceso aleatorio, es importante planear lo que vamos a almacenar en ellos; siempre se debe planear, pero en esta ocasión es un poco más importante, más que nada porque, como ya he dicho, tenemos la obligación de saber la longitud de los datos, (al menos de cada uno de los registros), a almacenar. Ya que esa longitud, (la de cada registro), debemos especificarla a la hora de abrir el fichero.

Un pequeño detalle antes de continuar, cuando abrimos un fichero secuencial para lectura (Input), el Visual Basic o el sistema operativo, sólo nos permite leer datos de ese fichero, es decir: no podemos leer y escribir al mismo tiempo. Igualmente ocurre cuando lo abrimos para escritura (Output), sólo que en esta ocasión sólo podemos escribir y no leer. Sin embargo, con los ficheros abiertos como aleatorios (Random) o binarios (Binary), una vez que el fichero esté abierto, podemos leer o escribir indistintamente. Alguna ventaja teniamos que tener... no iban a ser todo "obligaciones".

Sigamos con lo nuestro...

Veamos cómo, por fin, usar estos ficheros:
Para verlo... nada mejor que con un ejemplo:

Primero definimos un tipo de datos, en este caso vamos a guardar el nombre, edad y la cuenta de email de unos colegas, por tanto emplearemos un tipo definido con estos tres campos, veamos:

Private Type t_colegas
    Nombre  As String * 30
    Edad    As Integer
    email   As String * 50
End Type

Declaramos una variable de este "nuevo" tipo de datos, que será la que usaremos para acceder al fichero:

Dim unColega As t_colegas

Y abrimos el fichero:

Open "miscolegas.dat" For Random As nFic Len = Len(unColega)

El Len(unColega), le está indicando al VB que la longitud de cada registro es la que tenga ese tipo de datos.
No siempre hay que hacerlo así, podríamos asignar a una variable esa longitud y usarla para abrir el fichero:

lenColega = Len(unColega)
Open "miscolegas.dat" For Random As nFic Len = lenColega

Lo que quiero dejar claro es que al Visual Basic no le importa con qué variable accedemos al fichero, sino que longitud tendrá esa variable, o si lo prefieres, cual será la longitud de cada registro.
Por tanto, si sabemos que nuestro tipo de datos, (o lo que es lo mismo: cada registro), tiene 82 bytes de longitud, podríamos usar un string con esa longitud para acceder al fichero en cuestión y...

¡Toc, toc! ¡¡¡Guille!!!
¿Sí?
¿No crees que te estás precipitando?
¿Yo? ¿Por qué?
Porque estás hablando de acceder así o asao a los datos y aún no has explicado cómo leer o escribir esos datos...
Pues... sí... es cierto... je...

Si no fuera por el otro Guille... en fin...

Esto..., cuando quieras leer el contenido de un registro en particular del fichero abierto, usa esta instrucción:
Get #canal, num_registro, variable_de_datos

Para escribir usa esta otra:
Put #canal, num_registro, variable_de_datos

Es decir GET para leer y PUT para escribir, en esto los ingleses lo tienen más fácil, ya que al menos a ellos esas dos palabras tienen algo de sentido...
Después de cualquiera de estas instrucciones se indica el número de fichero (#canal), seguido por el número de registro al que queremos acceder, (num_registro), y por último la variable, (variable_de_datos), que usamos para leer, (GET), los datos del disco o almacenarlos en él (PUT)

Un detalle que tienes que tener siempre presente al definir un tipo de datos para acceder a los ficheros aleatorios: asegurate de que todas las cadenas contenidas en ese tipo, sean de longitud fija; lo mismo si dentro del tipo usas arrays, estos deben ser de índices constantes, es decir que estén definidos al crear el tipo.
Todas estas "precauciones" se consiguen con datos "estáticos", es decir que permanecen sin cambios, al menos en lo que a tamaño se refiere. Los "dinámicos" son los que pueden cambiar "dinámicamente" de tamaño.
Aclaremos estos puntos antes de continuar.

Una variable declarada de esta forma:
Dim cadenaEstatica As String * 50
Siempre tendrá 50 caracteres, incluso si hacemos esta asignación:
cadenaEstatica = ""
Con esto no eliminamos las 50 celdas de memoria, sino que la rellenamos de espacios...

Prueba con este código:
Crea un nuevo proyecto y añade esto en el Form_Load:

Private Sub Form_Load()

    Dim cadenaEstatica As String * 50

    Show

    cadenaEstatica = "Hola"
    Print cadenaEstatica & "Pepe"
    cadenaEstatica = ""
    Print cadenaEstatica & "Pepe"
    Print
    Print "Longitud de la cadena:"; Len(cadenaEstatica)
    Print "Asc(cadenaEstatica) ="; Asc(cadenaEstatica)
End Sub

El resultado sería este:

Es decir que una cadena estática (o de longitud fija), siempre tendrá el número de caracteres que le indicamos al declararla.
Sin embargo una cadena dinámica sólo tendrá los caracteres que le asignemos.

Prueba este código:

Private Sub Form_Load()

    Dim cadenaDinamica As String

    Show

    cadenaDinamica = "Hola"
    Print cadenaDinamica & "Pepe"
    cadenaDinamica = ""
    Print cadenaDinamica & "Pepe"
    Print
    Print "Longitud de la cadena:"; Len(cadenaDinamica)
    Print "Asc(cadenaDinamica) ="; Asc(cadenaDinamica)

End Sub

Habrás obtenido algo parecido a esto:

O sea "Illegal Function Call", este error lo da al intentar conseguir el código ASCII de la cadena... pero como la cadena está vacía... pues no hay nada que obtener, por tanto: ¡¡¡Error al canto!!!
Aparte del error, te puedes fijar que "HolaPepe" sale junto y que la longitud de la variable es cero.
Cuando asignamos una cadena vacía a un string dinámico, lo dejamos "seco", es decir sin nada en el interior...

Lo mismo ocurre con los arrays, ya sean de caracteres o numéricos.
Los estáticos siempre conservan el número de elementos, incluso cuando se eliminan, (al menos eso es lo que parece), con Erase. Aunque esto ya lo vimos, no está de más recordarlo: Al "eliminar" con Erase un array dinámico, lo eliminamos de la memoria, pero en los estáticos, simplemente ponemos el contenido a cero, (o a cadenas vacías si son de cadenas dinámicas).
Pruébalo:

    Dim arrayEstaticoInteger(1 To 10) As Integer
    Dim arrayEstaticoString(1 To 5) As String

    Dim arrayDinamicoInteger() As Integer
    Dim arrayDinamicoString() As String

Cuando se declaran los arrays con el número de elementos que va a contener, siempre con constantes, estos arrays son estáticos, porque siempre contendrán ese número de elementos.
Sin embargo los dinámicos, se declaran sin decirles cuantos elementos van a contener y después se usa Redim o Redim Preserve para cambiar el número de elementos.

Veamos un ejemplo de un tipo definido con un array estático:

Private Type t_colegas2
    Nombre          As String * 30
    Edad            As Integer
    email(1 To 3)   As String * 50
End Type

Aquí hemos definido un "campo" email con un array de tres elementos.

Recuerda que aunque los tipos definidos permitan tanto cadenas de longitud variable como arrays dinámicos, si ese tipo se va a usar para acceder a datos de un fichero aleatorio, su longitud siempre tiene que ser fija y coincidir con la longitud que se indicó a la hora de abrir ese fichero... si no tienes esta "precaución"... no accederás con "fiabilidad" a los datos contenidos en ese fichero... después no digas que no te advertí...

De todas formas, vamos a comprobarlo...
Escribe este código en un form:

'Este código en las declaraciones del form
Option Explicit

Private Type t_colegas
    Nombre  As String * 30
    Edad    As Integer
    email   As String * 50
End Type
Dim unColega As t_colegas

Private Type t_colegaDinamico
    Nombre  As String
    Edad    As Integer
    email   As String
End Type
Dim unColegaDinamico As t_colegaDinamico

Private Sub Form_Load()
    Dim nFic As Integer

    nFic = FreeFile
    Open "miscolegas.dat" For Random As nFic Len = Len(unColega)

    'Asignamos los datos
    With unColega
        .Nombre = "Pepe"
        .Edad = 22
        .email = "pepe22@mundo.net"
    End With
    Put #nFic, 1, unColega

    'ahora lo hacemos con el colega dinámico
    With unColegaDinamico
        .Nombre = unColega.Nombre
        .Edad = unColega.Edad
        .email = unColega.email
    End With
    'Aquí dará error:
    Put #nFic, 2, unColegaDinamico
    Close

End Sub

Ejecuta el programa y te encontrarás con un "bonito" error número: 59 Bad Record Length o La longitud de registro es incorrecta, si usas la versión castellana del Visual Basic. Lo que nos quiere decir este error es que no puedes grabar datos de una longitud diferente a la especificada a la hora de abrir el fichero.

Pero... ¿por qué? si, aparentemente, la longitud es la misma...
Pues eso, que sólo es en apariencia... si en la ventana "Inmediato" escribes esto, saldrás de dudas:
Print len(uncolega); len(uncolegadinamico)

El resultado será este:
82 10

En el primer caso, el del coleguilla normal, la longitud es la esperada: 82
Pero en nuestro amigo dinámico, la longitud es 10, independientemente de que "en teoría" tenga almacenados esos 82 caracteres del "estático". Lo que ocurre es que al ser dinámico, el contenido de las variables de caracteres se almacenan en otro sitio y lo único que hay en esas variables es un "puntero" a la dirección de memoria en la que están los datos almacenados... al menos ahora sabemos que una variable de cadena de caracteres tiene una longitud de 4 bytes... creo que eso ya lo vimos en la entrega número dos o tres... dónde hablamos de lo que ocupa cada variable... los enteros (Integer) ocupan 2 bytes, independientemente de cómo esté declarada la variable... lo mismo ocurre con los otros datos numéricos.

Como te decía, si sabemos que la longitud del registro es de 82 caracteres, podemos definir una cadena de esa longitud y usarla para acceder a los datos.
Probemos esto otro:

    Dim unaCadena As String * 82

'...
    unaCadena = "Prueba de una cadena"
    Put #nFic, 2, unaCadena
'...

Ahora si que funcionará.
Pero si esa variable está definida como String normal, no funcionará...

Resumiendo: para acceder a los registros de un fichero abierto como Random, las variables usadas deben tener definida la longitud igual que el tamaño especificado a la hora de abrirlo.

Un poco de complicación: Vamos a ver un ejemplo, "ilustrativo" más que práctico.
Ya he comentado que con una cadena de longitud fija podemos acceder a un registro. Ahora comprobarás lo "cómodo" que es usar los tipos definidos. Pero este ejemplo lo pongo para que sepas cómo se almacenan los datos numéricos en una cadena que se va a usar para guardar datos en un fichero de acceso aleatorio.
Hemos "comprobado" que un Integer se almacena en dos bytes... por tanto lo que se guarda en el disco es precisamente eso: dos bytes. Cuando se usa un tipo definido, no te tienes que preocupar de cómo se almacena, simplemente asignas el valor a la variable numérica y del resto se ocupa el VB.

Viendo el ejemplo anterior, es fácil almacenar un entero, si usamos un tipo definido, pero ¿cómo lo haremos si la variable en la que se almacenará los datos es una cadena de longitud fija?
Para ello debemos "desglosar" los datos en distintas partes:
Para el Nombre usaremos los primeros 30 bytes de la cadena, para la edad los dos siguientes, es decir: bytes 31 y 32, para el email desde la posición 33 hasta la 82, o sea desde la 33 con 50 bytes de longitud.

Si escribes esto:

Dim unaCadena As String * 82
Dim elNombre As String * 30
Dim laEdad As String * 2
Dim elEmail As String * 50

Dim nFic As Integer
Dim unaCadena As String * 82

Show

nFic = FreeFile
Open "miscolegas.dat" For Random As nFic Len = Len(unColega)


elNombre = "Pepe de 23 años"
laEdad = 23
elEmail = "Pepe23@mundo.net"

unaCadena = elNombre & laEdad & elEmail
Put #nFic, 2, unaCadena

Close nFic

No conseguirás el resultado esperado... en lugar de 23 años, tendrá: ¡¡¡ 13106 !!! Dudo mucho que llegue a conseguir esa edad... ni aún siendo un gnomo...
Si queremos guardar un número debemos convertirlo antes... el problema es que "NO HAY FUNCIONES DE CONVERSIÓN"
Antes si que las había... cuando digo antes, me refiero al Basic del MS-DOS.
Para cada tipo de dato numérico existían un par de funciones, una para convertir el número en una cadena y el otro para convertir la cadena en un número...
Y no me estoy refiriendo a las funciones que convierten los números en letras y las cadenas en números... aunque pueda parecerte una contradicción... no es lo mismo convertir un número en una cadena normal que en una cadena para almacenar en el disco como si de un número se tratase...

A saber, STR o CSTR convierten un número en una cadena, es decir que si el número es 23, lo convierten en "23" en el caso de CSTR y en " 23" si se usa STR, fíjate en el espacio que hay antes del 2 en el segundo caso.
Pero cuando se debe convertir un número en una cadena de 2 bytes... la cosa cambia...
Y no te confundas... no pienses que si usas CSTR lo tienes solucionado... porque en el ejemplo este de "23" está claro... pero y si el número es 1998? Tendríamos una cadena de 4 caracteres...

Como te decía, en los tiempos del MS-DOS, existían las funciones MKI$ y CVI para convertir los números enteros; MKL$ y CVL para los enteros largos, MKS$ y CVS para los "singles" y MKD$ y CVD para los Double. No busques estas funciones... porque no están...
Aunque podemos fabricárnoslas... pero, como no es plan de "romperse" el coco con una chorradilla de estas... sólo vamos a usar el ejemplo de los números enteros.

¿Cómo se usarían?
En el ejemplo anterior haríamos esto:

elNombre = "Pepe de 23 años"
laEdad = Mki(23)
elEmail = "Pepe23@mundo.net"

unaCadena = elNombre & laEdad & elEmail
Put #nFic, 2, unaCadena

La función se haría así:

Private Function Mki(ByVal unInteger As Integer) As String
    Dim sLoByte As String
    Dim sHiByte As String
    Dim iChar As Integer

    iChar = unInteger \ 256
    sHiByte = Chr$(iChar)
    iChar = unInteger Mod 256
    sLoByte = Chr$(iChar)
    'Devolvemos el valor
    Mki = sLoByte & sHiByte
End Function

Que complicación ¿verdad?
Pues como estas... muchas más... pero eso antes, en el MS-DOS... desde que hay "mogollón" de espacio de almacenamiento... he perdido de vista muchas de estas cosas de "manejo" de números a "bajo nivel"...
Pero antes había que ahorrar cuantos bytes se pudieran...

La cuestión es que se divide el número en dos partes, la alta que se consigue dividiendo el entero entre 256 y la baja, que es el resto que queda... después de haberlo dividido por 256... que de eso es de lo que se encarga el MOD...
Después se convierten esos valores en dos bytes usando el CHR$... se unen... y sin necesidad de agitarlo... conseguimos el "batido" que queremos... por tanto, lo asignamos al valor que devolverá la función... (recuerda que una función devuelve un valor cuando se asigna ese valor a su nombre...)

Bien... un lío... ¿verdad?
Pues ahora más líos...
¿Cómo se asigna esto al revés? Es decir: ¿Cómo se convierte una cadena de 2 bytes en un entero?
Con los tipos definidos no hay que hacer nada... ya sabes, que hace la conversión automáticamente.
Pero si usamos una cadena de longitud fija... ya es otro cantar...

Vamos a ver la declaración de la función "equivalente" al CVI del viejo Basic del MS-DOS:

Private Function Cvi(ByVal unaCadena As String) As Integer
    Dim sLoByte As String
    Dim sHiByte As String

    sLoByte = Left$(unaCadena, 1)
    sHiByte = Right$(unaCadena, 1)

    'Devolvemos el valor
    Cvi = Asc(sLoByte) + Asc(sHiByte) * 256
End Function

No voy a entrar en detalles, así que paso a un ejemplo "completo" para ver estos resultados.
Si vas a comprobar cómo "trabajan" alguna de estas funciones usando "puntos de interrupción", es decir que quieres que el VB se detenga en una línea determinada, (para ello deberás posicionarte en la línea, que no debe ser un comentario, y pulsar F9, al llegar a esa línea el Visual se detiene), deberás cambiar la propiedad Autoredraw del form y ponerla a TRUE, de esta forma el contenido del Form (el que se imprime dentro del Form_Load), se mantiene...

'Esto escribelo en el Form_Load
    Dim nFic As Integer
    Dim unaCadena As String * 82
    Dim laEdad23 As Integer

    Show
    laEdad23 = 23

    nFic = FreeFile
    Open "miscolegas.dat" For Random As nFic Len = Len(unColega)

    'Asignamos los datos
    With unColega
        .Nombre = "Pepe"
        .Edad = 22
        .email = "pepe22@mundo.net"
    End With
    Put #nFic, 1, unColega


    Dim elNombre As String * 30
    Dim laEdad As String * 2
    Dim elEmail As String * 50

    elNombre = "Pepe de 23 años"
    laEdad = Mki(laEdad23)
    elEmail = "Pepe23@mundo.net"

    unaCadena = elNombre & laEdad & elEmail
    Put #nFic, 2, unaCadena

    Get #nFic, 1, unColega
    With unColega
        Print
        Print "Colega 1:"
        Print .Nombre
        Print .Edad
        Print .email
    End With

    'Si se lee así, no hay problemas
    Get #nFic, 2, unColega
    With unColega
        Print
        Print "Colega 2:"
        Print .Nombre
        Print .Edad
        Print .email
    End With

    'De esta manera está la cosa más complicada...
    Get #nFic, 2, unaCadena
    Print
    Print "Colega 2:"
    Print Mid$(unaCadena, 1, 30)
    Print Cvi(Mid$(unaCadena, 31, 2))
    Print Mid$(unaCadena, 33, 50)

Observa que aunque el segundo dato se haya guardado usando una cadena de longitud fija, (unaCadena), se puede leer tanto con un tipo definido, si es de la misma longitud, claro, como con la cadena de longitud fija.
El problema es que tenemos que "desglosar" el contenido de esa cadena... Recuerda que te comenté que cada uno de los "campos" del registro ocupa un espacio determinado... o sea que tienen una longitud fija... por eso hay que usar el Mid$, para tomar cada uno de los "trozos" de esa cadena.

El Mid$ funciona de la siguiente forma:
Mid$ (cadena, posición_de_inicio, numero_de_caracteres)
Lo que significa que devolverá el número de caracteres indicados por numero_de_caracteres empezando por la posición: posición_de_inicio. En caso de que no se especifique el número de caracteres, devolverá toda la cadena desde la posición indicada hasta el final de la misma.

Y ya que estamos con estas funciones... voy a explicarte las dos usadas en la función CVI:
Left$ y Right$
El Left$ toma de una cadena los caracteres que se le indiquen, empezando por la izquierda.
Right$ hace lo mismo, pero empieza a contar desde la derecha.

Unos ejemplos:
Left$("Hola Mundo", 4) devolverá "Hola", es decir los 4 primeros caracteres.

Right$("Hola Mundo", 4) devolverá "undo", porque son los cuatro caracteres que hay a la derecha...

El Left$ se puede sustituir por Mid$ de la siguiente forma:
Mid$("Hola Mundo", 1, 4) es decir: empezando por el primer caracter, dame cuatro...

Sin embargo esto otro:
Mid$("Hola Mundo", 4) nos dará: "a Mundo", o lo que es lo mismo: desde la posición cuarta, todo lo que haya en la cadena.

Y si quieres imprimir cada uno de los caracteres de una cadena, prueba esto:

    Dim i As Integer
    Dim unaCadena As String

    unaCadena = "Hola Mundo"
    For i = 1 To Len(unaCadena)
        Print Mid$(unaCadena, i, 1)
    Next

Lo que se hace aquí es un bucle para cada uno de los caracteres de la cadena, esto se sabe con LEN, que devuelve la longitud de una cadena y con el Mid$, vamos tomando todos los caracteres de uno en uno. Ya que le decimos que nos muestre el caracter que está en la posición i... sólo muestra uno, porque esa es la cantidad que le indicamos que nos devuelva...

¡UF! Creo que algunas veces se me va la olla... y se me olvidan cosas que... creyendo que he explicado... las uso...
En fin... la cuestión es que ahora sabes unas instrucciones nuevas...

Y ya que estamos...
Veamos cómo se puede usar también MID$, lo que ocurre es que esta "función" también es una "instrucción", al igual que ocurría con INPUT, según se use delante o detrás del signo igual, se convierte en función o en instrucción.

Ya sabes que una función devuelve un valor y se puede usar en una expresión, pero una instrucción "hace" algo y no devuelve ningún valor ni se puede usar en una expresión.

En el ejemplo de guardar los registros, se usaron tres variables para almacenar los datos en el fichero:

    Dim elNombre As String * 30
    Dim laEdad As String * 2
    Dim elEmail As String * 50

    elNombre = "Pepe de 23 años"
    laEdad = Mki(laEdad23)
    elEmail = "Pepe23@mundo.net"

    unaCadena = elNombre & laEdad & elEmail
    Put #nFic, 2, unaCadena

Pues esto mismo se puede hacer de esta otra forma:

    Mid$(unaCadena, 1, 30) = "Prueba de una cadena"
    Mid$(unaCadena, 31, 2) = Mki(laEdad23)
    Mid$(unaCadena, 33, 50) = "pepe23@mundo.net"

    Put #nFic, 2, unaCadena

Es decir, al igual que la función con el mismo nombre, la instrucción MID$ sustituye en "unaCadena" los caracteres indicados por el inicio y la longitud por los que están después del signo igual...

Creo que me he pasado... bueno, esto al final viene bien... ya que así tenemos otra entrega "medio" preparada... ya que de la que tenía prevista de 7 páginas "manuscritas" sólo he usado 2 y poco más...

Para completar esta "extraña" entrega... extraña por "no prevista"... vamos a "rematarla" con unos ejercicios, que en este caso serán de manejo de cadenas con las funciones que acabamos de ver...

Los ejercicios:

1- Haz que se muestren los caracteres de una cadena al revés.
Por ejemplo, si la cadena es "Hola Mundo", se deberá imprimir: "odnuM aloH"
Que es útil si queremos hacer carteles para que se vean al derecho al mirar por el retrovisor del coche... (por decir algo)

2- Usando esta misma cadena, es decir "Hola Mundo", divídela en las dos palabras.
Se supone que deberá servirte para cualquier frase, así que no hagas nada "fijo".
Lo que tienes que hacer es devolver en una cadena todos los caracteres hasta el primer espacio y el resto en otra cadena.
Para esto, si quieres, puedes usar el Left$ y el Mid$... al menos para la primera palabra.

Y ya está... no te voy a complicar más la vida... que ya vendrán complicaciones posteriores.

Pulsando en este link tienes las soluciones.

 

Para terminar, sólo pedirte disculpas por haberme/haberte "liado"... pero... eso es lo que hay... 8-)

 

Y como no es plan de perder la costumbre de pedirte que hagas un comentario de la entrega que acaba de terminar... aquí tienes el link para que me hagas saber que te ha parecido...
A estas alturas no tendría que recordarte que ese link sólo es para hacer comentarios de esta entrega o del curso en general... así que no te voy a decir que no aproveches el link para hacerme consultas...

Nos vemos.
Guillermo


 
entrega anterior ir al índice siguiente entrega

Ir al índice principal del Guille