21 de enero de 2005

Resolución General 1702 de la AFIP (Argentina)

La Resolución General 1702 de la AFIP (Argentina) entrará en vigencia a principios del año 2005, por lo que en muchos foros de desarrolladores en Visual FoxPro se planteó la inquietud de que si con informes de Visual FoxPro y las rutinas y fuentes True Type del artículo de PortalFox "Codigos de barras en informes de VFP" se puede cumplimentar ésta resolución.

La respuesta es SI. Solo se debe modificar ligeramente la rutina correspondiente al código de barra "Interleaved 2 of 5" para que ésta retorne la cadena codificada lista para imprimir con la tipografía elegida, el dígito verificador y la cadena apta para la lectura humana. De todos modos existe en el artículo mencionado la fuente "PF Interleavev 2 of 5 Text" que contiene la cadena con lectura humana en su parte inferior.

Para el retorno de estos valores, lo mejor es crear una clase que llene los valores correspondientes en propiedades para poder ser utilizados. Para ello, la clase tiene las siguientes 3 propiedades:

  • DigitoVerificador: El dígito verificador de la cadena generada.
  • CodigoBarra: La cadena codificada lista para imprimir con la fuente True Type "PF Interleaved 2 of 5".
  • LecturaHumana: La cadena apta para la lectura humana.

La clase y un simple ejemplo de como utilizarla es el siguiente:

LOCAL lo
lo = CREATEOBJECT("I2of5")
lo.Codificar("201688627180100011234567890123420041231")
? SPACE(1) + lo.DigitoVerificador
? SPACE(1) + lo.CodigoBarra FONT "PF Interleavev 2 of 5 Text",36
? SPACE(4) + lo.CodigoBarra FONT "PF Interleaved 2 of 5",36
? SPACE(12) + lo.LecturaHumana FONT "Arial",13
lo = Null

*------------------------------------------------------------
* CLASS I2of5 AS CUSTOM 
*------------------------------------------------------------
* Clase que codifica una cadena a INTERLEAVED 2 OF 5
* METODOS: Codificar(tcTexto)
* PROPIEDADES: 
*   CodigoBarra: Texto para se impreso con fuente True Type 
*     "PF Interleaved 2 of 5" ó "PF Interleavev 2 of 5 Text"
*   DigitoVerificador = Digito verificador
*   LecturaHumana = Texto para Lectura Humana
* USO: 
*   lo = CREATEOBJECT("I2of5")
*   lo.Codificar("201688627180100011234567890123420041231")
*   ? lo.DigitoVerificador
*   ? lo.CodigoBarra FONT "PF Interleavev 2 of 5 Text",36
*   ? lo.CodigoBarra FONT "PF Interleaved 2 of 5",36
*   ? lo.LecturaHumana FONT "Arial", 13
*   lo = Null
*   RELEASE lo 
*------------------------------------------------------------
DEFINE CLASS I2of5 AS CUSTOM
  CodigoBarra = ""
  DigitoVerificador = ""
  LecturaHumana = ""
  PROCEDURE Codificar(tcTexto)
    LOCAL lcStart, lcStop, lcLH, lcCB, ;
      lnLong, lnI, lnCont, lnSum, lnAux
    lcStart = CHR(40)
    lcStop = CHR(41)
    lcLH = ALLTRIM(tcTexto)
    *--- Genero dígito de control
    lnLong = LEN(lcLH)
    lnSum = 0
    lnCont = 1
    FOR lnI = lnLong TO 1 STEP -1
      lnSum = lnSum + VAL(SUBSTR(lcLH,lnI,1)) * ;
        IIF(MOD(lnCont,2) = 0,1,3)
      lnCont = lnCont + 1
    ENDFOR
    lnAux = MOD(lnSum,10)
    lcDV = ALLTRIM(STR(IIF(lnAux = 0,0,10 - lnAux)))
    lcLH = lcLH + lcDV
    lnLong = LEN(lcLH)
    *--- La longitud debe ser par
    IF MOD(lnLong,2) # 0
      lcLH = '0' + lcLH
      lnLong = LEN(lcLH)
    ENDIF
    *--- Convierto los pares a caracteres
    lcCB = ''
    FOR lnI = 1 TO lnLong STEP 2
      IF VAL(SUBS(lcLH,lnI,2)) < 50
        lcCB = lcCB + CHR(VAL(SUBS(lcLH,lnI,2)) + 48)
      ELSE
        lcCB = lcCB + CHR(VAL(SUBS(lcLH,lnI,2)) + 142)
      ENDIF
    ENDFOR
    THIS.DigitoVerificador = lcDV
    THIS.LecturaHumana = lcLH
    THIS.CodigoBarra = lcStart + lcCB + lcStop
  ENDPROC
ENDDEFINE
*------------------------------------------------------------

NOTA: Recordar que las fuentes True Type deben estár instaladas en la PC para que los ejemplos funcionen correctamente. Las fuentes "PF Interleaved 2 de 5" se pueden descargar de: FuentesI2OF5.ZIP (17,10 KB)

La Resolución General 1702 de la AFIP (Argentina), (que se puede leer desde el sitio de la AFIP http://www.afip.gov.ar/afip/resol170204.html), establece que los datos que debe contener la cadena que debe ser impresa en los distintos tipos de comprobantes son:

  • C.U.I.T. (Clave Unica de Identificación Tributaria) del emisor (11 caracteres).
  • Código de tipo de comprobante (2 caracteres).
  • Punto de venta (4 caracteres).
  • C.A.I. (Código de Autorización de Impresión) (14 caracteres).
  • Fecha de vencimiento (8 caracteres).
  • Dígito verificador (1 caracter).

Esta cadena debe estar codificada con el código de barras Interleaved 2 of 5 (Entrelazado 2 de 5) y debe contener en la parte inferior la cadena completa y apta para lectura humana.

Un proyecto de ejemplo en VFP, con un formulario con el ingreso de los datos, un informe y la clase arriba descripta lo pueden descargar de RG1702.ZIP (10,10 KB)

Estas son las imágenes del proyecto de ejemplo de la descarga:

Espero que este ejemplo haya sido ilustrativo y útil para cumplimentar la R.G.1702 con un informe en Visual FoxPro. Si necesita mas información sobre códigos de barras en informes de VFP puede acceder al siguiente artículo: Codigos de barras en informes de VFP

Hasta la próxima.

Luis María Guayán

20 de enero de 2005

¿Que es un Framework?

Navegando en el historial de Google.Groups me encontré con este mensaje que nos dá una excelente cátedra sobre lo que es un framework. Centrándose principalmente en CodeBook y COMCodebook. Aunque lo aquí tratado aplica a cualquier Framework de cualquier lenguaje (es decir, suele ser universal el concepto).

***************************************
*Autor:Claudio Campos (infox@ciudad.com.ar)
*Asunto:Re: Proyecto de FrameWork en Español
*Grupos de noticias:microsoft.public.es.vfoxpro
*Fecha:2000-10-10 17:49:37 PST
***************************************

Un framework es una abstraccion de una maquina/artefacto (de construccion basada en ciclos de experiencia) para resolver un problema en UN contexto (ojo no confundir con pattern que es para resolver un problema en CUALQUIER contexto).

Y es por eso que en realidad no deberias pensar mucho en sus detalles, un framework condensa experiencia. Vos debes limitarte a instanciar esa maquina (framework) pero no tienes necesidad inmediata de conocer cada detalle de ella (estudiar cada clase por ejemplo).

El lado bueno es que (ademas de obtener un feedback de alimentacion de conocimientos, es decir 'ver' una arquitectura o poder 'copiar' o aprender como funciona una maquina), son los tiempos de desarrollo. Alguien que tiene que sacar un proyecto 'rapidamente' y quizas en un lenguaje que no conoce, podria facilmente aprender un framework (mas economico que armarse uno) ademas del lenguaje por supuesto y focalizarce en la solucion del problema del dominio (ojo no confundir usar un frameworks con usar wizards, builders o generadores).

Eso NO significa que NO podes hacer 'adaptaciones' o 'extensiones' a esa maquina, o de repente cambiar sus partes. Pero para eso hay tiempo y es cuando ya tenes TU 'maquina' funcionando y has hecho tus ciclos de experiencia con el framework, es ahi donde podes empezar a descubrir sus intestinos.

No obstante una salvedad con respecto a los frameworks: el costo/riezgo de instanciar algo muy abstracto es mucho mas grande a medida que subis en grados de abstraccion. (clonar es mucho mas economico que instanciar, instanciar un objeto de una clase concreta es mas economico que uno de una clase mas abstracta, instanciar un objeto a partir de una clase es mucho mas economico que instanciar un framework... y asi sucesivamente... arriba de todos estan los patterns). Es por eso que usar una 'maquina' construida por otro nos obliga a jugar con ciertas reglas.

Mi consejo: Cuando alguien esta 'jugado' para sacar una aplicacion en 3 o 4 meses y encima no conoce la herramienta/ambiente, lo mejor es usar un framework junto con una base de capacitacion en el lenguaje que esta construido. Si vos optas por armarte un framework vas a estar minimo 1 año tratando de estabilizar algo que ya esta 'hecho' y quizas mejor.

Riesgos: Cuando identificas los riesgos de tu proyecto puede ser que entre otros te encuentres con que la tecnologia es demasiado compleja, esto te impacta en los costos y te genera retrasos, la solucion: centrarte en la arquitectura de tu aplicacion (no los detalles del lenguaje o frameworks por ejemplo) y comprar competencias externas para asesoramiento.

Finalmente la respuesta a tu pregunta: Con guia podes aprender un framework con la complejidad de codebook en una semana (full gas), sin guia quiza te lleve algunos meses (si encontrar el camino correcto, es decir la forma de 'aprender' a usar esa abstraccion), o quizas desistas.

COM Codebook es algo totalmente diferente a Codebook, aunque la arquitectura de COM Codebook se puede usar perfectamente para 'reemplazar' al Codebook anterior.

Cuando vos comenzas un desarrollo tenes dos tipos de complejidades: la accidental y la esencial. El problema del Codebook (original) creo yo es no
haber pensado mucho en la complejidad accidental que tienen los proyectos cuyas tools no se adecuan muy bien a un determinado paradigma (por ejemplo object-ORIENTED).

El esquema de las bases de datos relacionales no tiene la 'expresividad' suficiente para describir de manera directa la compleja relacion entre datos
y conducta en el mundo de la orientacion a objetos, este es el caso mas extendido de complejidad 'accidental': impedance mismatch.

Entonces podriamos decir que COM Codebook es un framework de persistencia para base de datos relacionales.

Saludos !

Claudio Campos
Rafaela, Santa Fe

13 de enero de 2005

Usando la clase Empty (VFP8 y VFP9)

Un pequeño ejemplo de cómo podemos darle uso a la clase Empty, y usarlo a nuestra conveniencia...
*---------------------------------------------------------------------
* EmptyObject(fieldsList)
* Receives a list of fields separated by comma
* Returns an empty object with empty properties
* Recibe una lista de campos sepados por comas
* Retorna un objeto Empty con propiedades basadas 
* en objetos empty
*---------------------------------------------------------------------

* Test:
Customer = EmptyObject("Name,Address,Phone,Contacts[5]")
Customer.Address = EmptyObject("Street,City,State,Zip,Country")
Customer.Name  = "John Doe"
Customer.Phone = "555-1234"
Customer.Address.City  = "Sunny Beach"
Customer.Address.State = "FL"
Customer.Address.Zip   = "12345"
Customer.Contacts[1]   = "Jane Doe"
Customer.Contacts[2]   = "Jim Doe"
*** Revise el objeto Customer
Set Step On && Check out Customer object
Return Customer

*---------------------------------------------------------------------
Function EmptyObject
    LParameters fieldsList As String

    If Empty(fieldsList) Then
        Return Null
    Endif

    Local Array fieldsArray[1]
    Alines(fieldsArray,fieldsList,.T.,",")

    Local oEmpty
    oEmpty = NewObject("Empty")

    Try
        For Each item In fieldsArray
            AddProperty(oEmpty,item,"")
        Endfor
    Catch
        oEmpty = Null
    Endtry

    Return oEmpty
EndFunc
*--------------------------------------------------------------------- 
Esta función fué tomada de los newsgroups de VFP en ingles (con permiso de su autor):
********************************************
* Subject: Empty Objects
* From: George Nava
* Date: 03/01/2005 7:39
* newsgroup: microsoft.public.fox.programmer.exchange
********************************************
Espero que les sea de utilidad.

Espartaco Palma, México

12 de enero de 2005

Usando AutoCompletar en Textboxs de VFP 9

En Visual FoxPro 9 contamos con algunas nuevas propiedades en los textboxs, en este articulo tratare de explicar el Uso del Autocompletar

Básicamente se compone de 3 propiedades

AutoCompSource: Nos Permite especificar el nombre con el que se buscara los posibles datos de referencia, util si queremos utilizar la misma lista en uno o mas textboxs, por default el dato usado es el nombre del textbox, pero podemos especificarle el propio usando esta propiedad.

AutoCompTable: Indica el nombre de la tabla en la cual se almacenaran los datos que sean introducidos en el textbox, por default la tabla que se usa para almacenar estos valores es: Autocomp.DBF almacenada en el directorio de datos del usuario actual (home(7)) pero podemos indicarle por medio de esta propiedad en que ruta y archivo queremos que se almacene.

AutoComplete: Nos permite indicar los diferentes comportamientos del autocompletar en el textbox, los posibles valores son:
  • 0 - Deshabilitado: No activa el autocompletar
  • 1 - Alfabético: Ordena los elementos Alfabéticamente
  • 2 - Mas Frecuentemente usado (MFU): Ordena las opciones basándose en la cantidad usada de cada opcion y luego la ordena por la opcion mas recientemente actualizada
  • 3 - Mas usada Recientemente: Ordena las opciones basadas en el campo "Updated" de la tabla usada para autocompletar, el cual es actualizado cada vez que se selecciona una opción.
  • 4 - Personalizado: usa el Campo "Weight" de la tabla utilizada para autocompletar, el desarrollador es responsable de actualizar el campo con su propio algoritmo para especificar el ordenamiento, las opciones con el valor mas alto en el campo "Weight" son mostradas primero.
A continuacion un ejemplo (copie y pegue en un archivo PRG y ejecute):
PUBLIC oFrm
oFrm=NEWOBJECT("frm_autocompletar")
oFrm.SHOW
RETURN
DEFINE CLASS frm_autocompletar AS FORM
  AUTOSIZE = .T.
  HEIGHT = 236
  WIDTH = 447
  DOCREATE = .T.
  CAPTION = "Usando Autocompletar"
  MAXBUTTON = .F.
  MINBUTTON = .F.
  NAME = "frm_autoCompletar"

  ADD OBJECT txt1 AS Txt WITH ;
    TOP = 30, ;
    AutoComplete = 1, ;
    AutoCompSource = "txtDemo"

  ADD OBJECT txt2 AS Txt WITH ;
    TOP = 80, ;
    AutoComplete = 2, ;
    AutoCompSource = "txtDemo"

  ADD OBJECT txt3 AS Txt WITH ;
    TOP = 133, ;
    AutoComplete = 3, ;
    AutoCompSource = "txtDemo"

  ADD OBJECT txt4 AS Txt WITH ;
    LEFT = 23, ;
    TOP = 192, ;
    WIDTH = 169, ;
    AutoComplete = 4

  ADD OBJECT txt5 AS Txt WITH ;
    LEFT = 203, ;
    TOP = 190, ;
    WIDTH = 169, ;
    AutoComplete = 4

  ADD OBJECT lbl4 AS Lbl WITH ;
    CAPTION = "Ordenamiento Por codigo (AutoComplete = 4)", ;
    TOP = 170

  ADD OBJECT lbl3 AS Lbl WITH ;
    CAPTION = "Ordenamiento Por ultima vez usado (AutoComplete = 3)", ;
    TOP = 114

  ADD OBJECT lbl2 AS Lbl WITH ;
    CAPTION = "Ordenamiento Por mas usado (AutoComplete = 2)", ;
    TOP = 61

  ADD OBJECT lbl1 AS Lbl WITH ;
    CAPTION = "Ordenamiento Alfabetico (AutoComplete = 1)", ;
    TOP = 10

  PROCEDURE txt4.VALID
    IF DODEFAULT()
      UPDATE (THIS.AutocompTable) SET weight = 1 WHERE ALLTRIM(UPPER(SOURCE)) = ;
        IIF(EMPTY(THIS.AutocompSource),ALLTRIM(UPPER(THIS.NAME)),ALLTRIM(UPPER(THIS.AutocompSource)))
      UPDATE (THIS.AutocompTable) SET weight = 0 WHERE ALLTRIM(UPPER(DATA)) = ;
        ALLTRIM(UPPER(THIS.VALUE)) AND ALLTRIM(UPPER(SOURCE)) = ;
        IIF(EMPTY(THIS.AutocompSource),ALLTRIM(UPPER(THIS.NAME)),ALLTRIM(UPPER(THIS.AutocompSource)))
      USE IN (THIS.AutocompTable)
    ENDIF
  ENDPROC

  PROCEDURE txt5.VALID
    IF DODEFAULT()
      UPDATE (THIS.AutocompTable) SET weight = 0 WHERE ALLTRIM(UPPER(SOURCE)) = ;
        IIF(EMPTY(THIS.AutocompSource),ALLTRIM(UPPER(THIS.NAME)),ALLTRIM(UPPER(THIS.AutocompSource)))
      UPDATE (THIS.AutocompTable) SET weight = 1 WHERE ALLTRIM(UPPER(DATA)) = ;
        ALLTRIM(UPPER(THIS.VALUE)) AND ALLTRIM(UPPER(SOURCE)) = ;
        IIF(EMPTY(THIS.AutocompSource),ALLTRIM(UPPER(THIS.NAME)),ALLTRIM(UPPER(THIS.AutocompSource)))
      USE IN (THIS.AutocompTable)
    ENDIF
  ENDPROC
ENDDEFINE

DEFINE CLASS Lbl AS LABEL
  AUTOSIZE = .T.
  BACKSTYLE = 0
  LEFT = 24
ENDDEFINE

DEFINE CLASS Txt AS TEXTBOX
  HEIGHT = 25
  LEFT = 24
  WIDTH = 265
  AutocompTable = "AutoCompletar"
ENDDEFINE

Para ver el efecto en acción, pruebe a ingresar en el primer textbox, 3 nombres: Juan, Pedro y Jose, (asegúrese de presionar la tecla Enter luego de terminar de escribir el nombre) luego Escriba Pedro (o Seleccionelo de la lista cuando se lo sugiera) 2 veces, Luego Escriba Jose si va al segundo Textbox luego de esto, al dar click en el control, le mostrara como primer elemento Pedro, ya que es el que mas se ha utilizado (dos veces mas que los otros en este ejemplo) ya que el segundo textbox utiliza la opcion "2- Mas Frecuentemente usado", (no presione Enter borre el contenido del segundo textbox de preferencia), Luego dirijase al tercer textbox y al dar click en el control podrá observar que el primer elemento sera Jose, ya que este utiliza la opción "3- Mas usada recientemente".

Ahora si Se Fija en los otros 2 textbox (el cuarto y quinto) estos utilizan la opcion "4- Personalizado", así que tienen código en el evento Valid para ajustar el campo "Weight" (peso en ingles) para ajustar el orden, el 4 textbox coloca el ultimo valor escrito o seleccionado en la ultima posición mientras que el quinto textbox coloca el ultimo valor escrito o seleccionado al inicio.

Saludos

Jorge Mota
Guatemala

10 de enero de 2005

Creando Formas irregulares con el Objeto Shape en VFP 9

A partir de VFP 9 podemos crear Shapes basandonos en poligonos, el cual se basa en un arreglo de cualquier dimension conteniendo las coordenadas X y Y en el orden en que debera ser dibujado.

La propiedad que se debe establecer hacia el arreglo que contenga las coordenadas es PolyPoints.

A continuación un Ejemplo de como usar esta nueva propiedad:
Public oFrm_Shape
oFrm_Shape=Newobject("frm_shape")
oFrm_Shape.Show
Return
Define Class frm_shape As Form
 DataSession = 2
 Top = 0
 Left = 0
 Height = 354
 Width = 592
 DoCreate = .T.
 Caption = "Creando Shapes Complejos"
 Name = "frm_shape"
 Dimension apuntos[1]
 Add Object shape1 As Shape With ;
  Top = 19, ;
  Left = 260, ;
  Height = 314, ;
  Width = 319, ;
  BackStyle = 1, ;
  BorderWidth = 3, ;
  BackColor = Rgb(0,128,255), ;
  BorderColor = Rgb(255,255,255), ;
  Name = "Shape1"
 Add Object spinner1 As Spinner With ;
  Height = 24, ;
  KeyboardHighValue = 50, ;
  KeyboardLowValue = 1, ;
  Left = 137, ;
  SpinnerHighValue =  50.00, ;
  SpinnerLowValue =   1.00, ;
  Top = 24, ;
  Width = 117, ;
  Value = 1, ;
  NullDisplay = "0", ;
  Name = "Spinner1"
 Add Object grid1 As Grid With ;
  ColumnCount = 3, ;
  FontSize = 8, ;
  DeleteMark = .F., ;
  Height = 284, ;
  Left = 3, ;
  Panel = 1, ;
  RecordSource = "cPuntos", ;
  RowHeight = 17, ;
  ScrollBars = 2, ;
  Top = 51, ;
  Width = 252, ;
  Name = "Grid1", ;
  Column1.FontSize = 8, ;
  Column1.ControlSource = "Recno()", ;
  Column1.Width = 94, ;
  Column1.Name = "Column1", ;
  Column2.FontSize = 8, ;
  Column2.ControlSource = "cPuntos.nPosX", ;
  Column2.Width = 53, ;
  Column2.Name = "Column2", ;
  Column3.FontSize = 8, ;
  Column3.ControlSource = "cPuntos.NPosY", ;
  Column3.Width = 55, ;
  Column3.Name = "Column3"
 Procedure odibujar
  Local nValor, nReg
  nReg=Recno("cPuntos")
  Select Count(nPosx) As Conteo From cPuntos Where !Deleted() Into Cursor cC
  nValor = cC.Conteo
  Use In Select('cC')
  Select cPuntos
  Copy To Array apuntos For !Deleted('cPuntos')
  Acopy(apuntos,Thisform.apuntos)
  Thisform.shape1.Polypoints=[thisform.apuntos]
  Go (nReg) In cPuntos
 Endproc
 Procedure Load
  Create Cursor cPuntos (nPosx N(3,0),nPosY N(3,0))
  Set Deleted On
 Endproc
 Procedure Init
  With This.grid1
   .Column1.header1.Caption = [Posición]
   .Column2.header1.Caption = [X]
   .Column3.header1.Caption = [Y]
   .SetAll([alignment],2,[Header])
  Endwith
  This.spinner1.Value = 19
  Local apuntos
  Dimension apuntos[19,2]
  Select cPuntos
  apuntos[1,1] = 5 
  apuntos[1,2] = 4
  apuntos[2,1] = 24
  apuntos[2,2] = 4
  apuntos[3,1] = 24
  apuntos[3,2] = 23
  apuntos[4,1] = 11
  apuntos[4,2] = 23
  apuntos[5,1] = 11
  apuntos[5,2] = 46
  apuntos[6,1] = 5
  apuntos[6,2] = 46
  apuntos[7,1] = 5
  apuntos[7,2] = 4
  apuntos[8,1] = 37
  apuntos[8,2] = 4
  apuntos[9,1] = 37
  apuntos[9,2] = 13
  apuntos[10,1] = 29
  apuntos[10,2] = 13
  apuntos[11,1] = 29
  apuntos[11,2] = 26
  apuntos[12,1] = 37
  apuntos[12,2] = 26
  apuntos[13,1] = 37
  apuntos[13,2] = 35
  apuntos[14,1] = 30
  apuntos[14,2] = 35
  apuntos[15,1] = 30
  apuntos[15,2] = 46
  apuntos[16,1] = 20
  apuntos[16,2] = 46
  apuntos[17,1] = 20
  apuntos[17,2] = 23
  apuntos[18,1] = 24
  apuntos[18,2] = 23
  apuntos[19,1] = 24
  apuntos[19,2] = 4
  Append From Array apuntos
  Thisform.odibujar()
 Endproc

 Procedure spinner1.InteractiveChange
  Thisform.LockScreen = .T.
  Local nReg, nValor,nDel
  Set Deleted Off
  Select Count(nPosx) As Conteo From cPuntos Where Deleted() Into Cursor cC
  nDel = cC.Conteo
  Use In Select('cC')
  Select cPuntos
  nReg=Reccount('cPuntos')-nDel
  nValor = This.Value
  If nReg >nValor
   Delete From cPuntos Where Recno()>nValor
  Else
   For nCiclo =  1 To Abs(nReg-nValor)
    Locate For Deleted('cPuntos')
    If Found()
     Recall
    Else
     Insert Into cPuntos (nPosx,nPosY) Values (1,20)
    Endif
   Endfor
  Endif
  Set Deleted On
  Copy To Array apuntos For !Deleted('cPuntos')
  Dimension Thisform.apuntos[Alen(apuntos,1),2]
  Acopy(apuntos,Thisform.apuntos)
  Thisform.Refresh
  Thisform.LockScreen = .F.
 Endproc

 Procedure grid1.AfterRowColChange
  Lparameters nColIndex
  DoDefault(nColIndex)
  Thisform.odibujar()
 Endproc
Enddefine
Saludos

Jorge Mota, Guatemala