Ficheros (II)
Cuando empezamos a trabajar con ficheros, a veces se nos plantean muchas dudas sobre el hecho de tener que leer de mas de un fichero a la vez.
Son muchas las preguntas que me habéis planteado sobre este tema, así que aquí, en esta segunda parte dedicada a los ficheros voy a intentar que quede resuelta con un ejemplo práctico.

La situación
Vamos a utilizar un fichero de albaranes que a su vez integra una tabla.
Dos ficheros maestros, clientes y productos.
Un fichero de impresora.

¿Que vamos a hacer?
Vamos a leer secuencialmente el fichero de albaranes. Al mismo tiempo vamos a extraer el nombre del cliente y además vamos a actualizar las existencias de los productos.
También vamos a ir generando una línea de impresora por cada albarán con el importe total del mismo.

Lo primero que hacemos es iniciar nuestro programa con las dos divisiones primeras, donde indicamos el nombre del programa y los ficheros con los que vamos a trabajar.

Empezamos con nuestro programa:
    IDENTIFICATION DIVISION.
    PROGRAM-ID. FICHEROS.
    ENVIRONMENT DIVISION.
    CONFIGURATION SECTION.
    SPECIAL-NAMES. DECIMAL-POINT IS COMMA.
    INPUT-OUTPUT SECTION.
    FILE-CONTROL.
    SELECT ALBARANES ASSIGN TO RANDOM "ALBARANES.DAT"
    ORGANIZATION INDEXED ACCESS DYNAMIC
    RECORD KEY ALB-NUMERO
    FILE STATUS STA-ALBA.
    SELECT CLIENTES ASSIGN TO RANDOM "CLIENTES.DAT"
    ORGANIZATION INDEXED ACCESS RANDOM
    RECORD KEY CLI-CODIGO
    FILE STATUS STA-CLIEN.
    SELECT PRODUCTOS ASSIGN TO RANDOM "PRODUCTOS.DAT"
    ORGANIZATION INDEXED ACCESS RANDOM
    RECORD KEY PRO-CODIGO
    FILE STATUS STA-PRODU.
    SELECT IMPRE ASSIGN TO PRINT "PRINTER1".
Como veis al archivo de albaranes le hemos indicado un acceso dinámico porque lo vamos a leer secuencialmente, mientras que a los ficheros maestros un acceso directo, ya que los vamos a acceder por su código.
A continuación describimos los campos de cada uno de los ficheros.

    DATA DIVISION.
    FILE SECTION.
    FD  ALBARANES LABEL RECORD STANDARD.
    01  REGALBA.
      02  ALB-NUMERO PIC 9(6).
      02  ALB-FECHA PIC 9(8).
      02  ALB-CLIENTE PIC 9(4).
      02  ALB-ELEMENTOS OCCURS 10 TIMES.
        03  ALB-PRODUCTO PIC 9(4).
        03  ALB-CANTIDAD PIC 9(6)V99.
        03  ALB-PRECIO PIC 9(6)V99.

    FD  CLIENTES LABEL RECORD STANDARD.
    01  REGCLIEN.
      02  CLI-CODIGO PIC 9(4).
      02  CLI-NOMBRE PIC X(40).
      02  CLI-DOMICILIO PIC X(40).
      02  CLI-POBLACION PIC X(30).
      02  CLI-PROVINCIA PIC X(20).
      02  CLI-NIF PIC X(10).

    FD  PRODUCTOS LABEL RECORD STANDARD.
    01  REGPRODU.
      02  PRO-CODIGO PIC 9(4).
      02  PRO-NOMBRE PIC X(40).
      02  PRO-EXISTENCIAS PIC S9(6)V99.
      02  PRO-PROVEEDOR PIC 9(4).
      02  PRO-UNIMEDIDA PIC X(20).

    FD  IMPRE LABEL RECORD OMITTED.
    01  LINEA PIC X(80).

........... En cuanto a las variables que vamos a utilizar:
Las de estado de cada fichero.
Contadores para la tabla, las líneas y los albaranes leidos.
Las líneas de nuestro listado.

    WORKING-STORAGE SECTION.
    01  ESTADOS.
      02  STA-ALBA PIC XX.
      02  STA-CLIEN PIC XX.
      02  STA-PRODU PIC XX.
    01  CONTA1 PIC 9(6).
    01  CONTALIN PIC 9(6).
    01  CONTA PIC 9(6).
    01  FINFIC PIC X.
    01  TOTAL PIC S9(8)V99.
    01  TOLINEA PIC S9(8)V99.
    01  TOALBA PIC S9(8)V99.

    01  LIN-01.
      02  FILLER PIC X(66) VALUE 'LISTADO DE ALBARANES'.
      02  FILLER PIC X(10) VALUE 'HOJA .. '.
      02  L-HOJA PIC ZZZZ.

    01  LIN-02.
      02  FILLER PIC X(9) VALUE 'ALBARAN'.
      02  FILLER PIC X(12) VALUE ' FECHA'.
      02  FILLER PIC X(46) VALUE 'CLIENTE'.
      02  FILLER PIC X(13) VALUE 'IMPORTE'.

    01  RAYA PIC X(80) VALUE ALL '='.
    01  DETALLE.
      02  L-ALBARAN PIC ZZZ.ZZZBB.
      02  L-FECHA PIC ZZ/ZZ/ZZZZBB.
      02  L-CLIENTE PIC ZZZZB.
      02  L-NOMCLI PIC X(40)B.
      02  L-IMPORTE PIC ZZ.ZZZ.ZZ9,99.

Ahora es el momento de la programación, de que todo salga tal y como hemos planteado.
Voy a ir incluyendo líneas de comentario para que la explicación quede mas detallada. En pocas palabras leemos, actualizamos, listamos, terminamos.

    PROCEDURE DIVISION.
    INICIO.
    Abrimos los ficheros cada uno en su modo adecuado, imprimimos la cabecera del listado e inicializamos las variables.
      OPEN INPUT ALBARANES CLIENTES I-O PRODUCTOS EXTEND IMPRE
      PERFORM CABECERA
      MOVE ' ' TO FINFIC MOVE 0 TO TOTAL CONTA
    Iniciamos el bucle de lectura hasta que se cumpla la condición de fin de fichero y movemos campos del fichero a variables de la línea de impresión.
      PERFORM UNTIL FINFIC = 'S'
        READ ALBARANES NEXT RECORD AT END MOVE 'S' TO FINFIC
        NOT AT END
        MOVE ALB-CODIGO TO L-ALBARAN
        MOVE ALB-FECHA TO L-FECHA
      Nos preparamos para leer clientes, tomamos su nombre y si no existe ponemos una aclaración.
        MOVE ALB-CLIENTE TO CLI-COD
        READ CLIENTES INVALID KEY MOVE 'NO EXISTE EL CLIENTE' TO CLI-NOMBRE
        END-READ
        MOVE CLI-NOMBRE TO L-NOMCLI
      Comenzamos otro bucle interno para acceder a los elementos de la tabla que contiene los productos, la cantidad y el precio del albarán. Además sumamos sus importes para totalizar el albaran. También, si el producto existe le quitamos la cantidad vendida de sus existencias y regrabamos.
        MOVE 0 TO TOALBA
        PERFORM VARYING CONTA1 FROM 1 BY 1 UNTIL CONTA1 > 10
          IF ALB-PRODUCTO (CONTA1) > 0
            MOVE ALB-PRODUCTO TO PRO-CODIGO
            READ PRODUCTO INVALID KEY
              DISPLAY 'PRODUCTO NO ENCONTRADO, NO SE ACTUALIZA'
              NOT INVALID KEY
              SUBTRACT ALB-CANTIDAD FROM PRO-EXISTENCIAS
              REWRITE REGPRODU INVALID KEY DISPLAY 'ERROR'
              END-REWRITE
            END-READ
            COMPUTE TOLINEA ROUNDED = ALB-CANTIDAD * ALB-PRECIO
            COMPUTE TOALBA ROUNDED = TOALBA + TOLINEA
          END-IF
        END-PERFORM
      Al totalizar el albaran imprimimos la linea correspondiente y comprobamos que no hemos llegado al final de la página. El salto lo realizaremos despues de imprimir 54 líneas.
        MOVE TO-ALBA TO L-IMPORTE
        COMPUTE TOTAL ROUNDED = TOTAL + TOALBA
        MOVE 0 TO TOALBA
        WRITE LINEA FROM DETALLE AFTER 1
        ADD 1 TO CONTALIN ADD 1 TO CONTA
        IF CONTALIN > 54 WRITE LINEA FROM RAYA AFTER 1
          PERFORM CABECERA
        END-IF
        END-READ
      END-PERFORM
    Una vez terminado el bucle de lectura. Imprimimos una línea con los totales acumulados, cerramos los ficheros y terminamos el programa.
      WRITE LINEA FROM RAYA AFTER 1
      MOVE CONTA TO L-ALBARAN
      MOVE 0 TO L-FECHA L-CLIENTE
      MOVE 'TOTAL GENERAL' TO L-NOMCLI
      MOVE TOTAL TO L-IMPORTE
      WRITE LINEA FROM DETALLE AFTER 1
      WRITE LINEA FROM RAYA AFTER 1
      CLOSE PRODUCTOS ALBARANES CLIENTES IMPRE
      STOP RUN.

Este párrafo es el que utilizamos para escribir la cabecera, tanto al principio como cada vez que llenamos una página.
    CABECERA.
      MOVE 0 TO CONTALIN ADD 1 TO HOJA MOVE HOJA TO LHOJA
      WRITE LINEA FROM LIN-01 AFTER PAGE
      WRITE LINEA FROM RAYA AFTER 2
      WRITE LINEA FROM LIN-02 AFTER 1
      WRITE LINEA FROM RAYA AFTER 1.
    ...........
Creo que con este pequeño programa de ejemplo, podéis comprender el funcionamiento de los ficheros.

Como habéis visto, hemos leido secuencialmente, hemos accedido directamente por código, hemos impreso líneas en una impresora y además hemos ido actualizando un fichero, con lo que todos los procesos que se pueden dar (mas o menos) han quedado reflejados.

Me gustaría comentar el tema de la apertura de ficheros.
Un archivo abierto como INPUT jamás tendrá exclusividad y podrá ser accedido por todos los usuarios que lo utilicen sin ningún problema de bloqueos. En cambio al abrirlo como I-O podemos tener problemas de bloqueos y además si se va la luz en ese momento o se apaga bruscamente el PC podemos llegar a ocasionar el estatus 98 y tener verdaderos problemas para recuperar el fichero.

Mi consejo particular, que es el que uso desde hace muchos años y jamás he tenido un problema ni de bloqueos ni de errores de ficheros, es abrir siempre el fichero como INPUT y solo en los momentos necesarios abrirlos como I-O. Nunca abrais los ficheros nada mas empezar el programa como I-O, no es necesario y en muchos casos solo nos va a traer problemas, al igual que habrá programas en los que no sea necesario tener un fichero abierto siempre y solo abrirlo cuando lo vayamos a utilizar.

Os repito que estos consejos son los que la experiencia me ha ido enseñando y hoy en día con los equipos informáticos que existen, la posible pérdida de tiempo en abrir y cerrar ficheros, pasa totalmente inadvertida para nosotros.

Para ver lo que os he explicado antes de una manera mas fácil, os pongo un ejemplo:

Empezamos nuestro programa y hacemos un:
    OPEN INPUT FICHERO
    ...
    ...
    el programa sigue su curso, pero si en algún momento necesitamos Borrar, Grabar o Regrabar algún registro, entonces actuamos así:
    ...
    CLOSE FICHERO OPEN I-O FICHERO
    REWRITE REGISTRO INVALID KEY DISPLAY 'ERROR'
    END-REWRITE
    CLOSE FICHERO OPEN INPUT FICHERO
    ...
    ...
Si en este caso, dos usuarios distintos estuvieran modificando el mismo registro y los dos le dieran a la vez a grabar, siempre se quedarán los datos del último que haya pulsado, pero jamás creará un problema de conflictos, ya que aunque los ordenadores sean muy rápidos, siempre tienen que llevar un orden en la ejecución de las instrucciones.

En la siguiente entrega, que será la última, veremos como asignar diferentes nombres a nuestros ficheros y como hacer un archivo secuencial para abrirlo con Excel, por ejemplo.



Este artículo proviene de Cobol en español
http://www.escobol.com

La dirección de esta noticia es:
http://www.escobol.com/modules.php?name=Sections&op=viewarticle&artid=95