Mostrando entradas con la etiqueta Calc. Mostrar todas las entradas
Mostrando entradas con la etiqueta Calc. Mostrar todas las entradas

viernes, 20 de diciembre de 2024

Análisis. Datos.

Acceso a tabla: columnas y filas.

Antes de iniciar el análisis de datos, y como paso previo, trabajando con OOo Basic es necesario aprender a acceder a las columnas (campos o variables, según se prefiera) y a las filas (o registros).


Ambos accesos se circunscriben al trabajo con OOo Basic como herramienta para el análisis de datos, ya que en otros lenguajes (R y Python) no son necesarios como tales, puesto que el proceso se resuelve de forma directa mediante las funciones correspondiente (1).

Lo que sí van a tener los tres lenguajes es que en el proceso de análisis de datos también en OOo Basic trabajaré directamente desde el IDE como espacio para la generación de script y el desarrollo del propio proceso de análisis. Como consecuencia de ello, y al contrario del planteamiento de trabajo que implica el desarrollo de docap, aquí no existirá otra interface que el propio IDE y el recurso a ventanas emergentes (MsgBox) como medio para que OOo Basic genere las salidas de datos. Esto significa que, en principio, no se prestará atención a la entrada de datos y a la salida sólo mediante escritura en la hoja de cálculo cuando resulte pertinente; el resto queda a la escritura de código y a la recopilación manual por parte del usuario. Es este un procedimiento extraño al objetivo para el que fue ideado OOo Basic, pero muy común en los lenguajes Python y R (especialmente este último) y empleándolo me quiero aproximar a ese modo de trabajo, eludiendo de paso la carga de trabajo que supone el diseño de interfaces de usuario. Se espera que a estas alturas del proceso de aprendizaje LO - OOo Basic este modo de uso del lenguaje no suponga ningún problema para el usuario.

Para concretar y ejemplificar ambos procedimientos de acceso a los datos emplearé una tabla ficticia que recoge el registro de actuaciones mensuales de un OE en un determinado centro a lo largo del curso (2). Sobre esta base, empezaré por explicar el acceso a una columna de datos, que es, posiblemente, el procedimiento más empleado en el análisis de datos.

Sub AccesoCol

Dim vN As Integer, i As Integer
Dim vHoja As String, vCol As String, vTipoDat As String
Dim mDatosCol() As Variant
Dim oHoja As Object, oCelda As Object

vN = 9
vHoja = "Datos"
vCol = "E"

ReDim mDatosCol(vN)

vTipoDat = "N" 'Tipo de datos a capturar: T - Texto y N - Numérico

oHoja = ThisComponent.getSheets().getByName(vHoja)

For i = 0 To UBound(mDatosCol())
oCelda = oHoja.getCellRangeByName(vCol & i+2)
If vTipoDat = "N" Then
mDatosCol(i) = CInt(oCelda.getString)
ElseIf vTipoDat = "T" Then
mDatosCol(i) = oCelda.getString
End If
Next

End Sub

Tras la declaración de las variables, asignamos valores a las variables que nos van a permitir dimensionar la matriz de datos (ReDim mDatosCol(vN)) y acceder a la hoja (vHoja = "Datos") y  a la columna (vCol = "E")  donde se encuentran dichos datos. Después seleccionamos el tipo de datos al que se va a acceder (vTipoDat = "N") (3), a fin de facilitar el uso de procedimiento adecuado a ese tipo de datos, según veremos un poco más abajo.

Lo siguiente que hacemos es acceder a la hoja (oHoja = ThisComponent.getSheets().getByName(vHoja)) y después, mednte un bucle (For i = 0 To UBound(mDatosCol())), a cada una de las celdas de la columna seleccionada (oCelda = oHoja.getCellRangeByName(vCol & i+2)). Como la instrucción de acceso es diferente en función del tipo de datos (textos o números), empleamos un condicional para acceder a números (If vTipoDat = "N" Then) -> (mDatosCol(i) = CInt(oCelda.getString)) u otro para acceder a textos (ElseIf vTipoDat = "T" Then) -> (mDatosCol(i) = oCelda.getString).

Y ya estamos en disposición de trabajar con esta matriz de datos que consideramos valores de la variable (en este caso) Actividad Tipo 1 (AT1), sea esta lo que sea.

Si quisiéramos acceder a una segunda variable, deberíamos crear una segunda matriz de datos y proceder del mismo modo, lo que implica complicar el script. En caso de que este script resulte demasiado complejo y se reduzca la funcionalidad de uso, sería conveniente plantearse la creación de una función o una subrutina en la que concretar aquellos procedimientos que se repiten varias veces en dicho script (4).

El acceso a una determinada fila | registro de la tabla, en principio, no es muy diferentes al modo que utilizamos para grabar los datos en una determinada fila, según quedó explicado en entradas anteriores [por ejemplo en esta], y que empleamos para crear el docap de generación de la tabla de datos [ver aquí], pero presenta algunas diferencias debidas al cambio de tratamiento de los datos.

Sub AccesoFil

Dim oHojaBD As Object, oCeldaInicio As Object, oCeldaRegistro As Object
Dim vN As Integer, i As Integer
Dim vHoja As String
Dim a As Integer, b As Integer, c As Integer, vFil As Integer
Dim mDatos() As String

vHoja = "Datos" ' Nombre de la hoja de datos

oHojaBD = ThisComponent.getSheets().getByName(vHoja)

vN = 7 'Nº de elementos de la fila (campos del registro)
ReDim mDatos(vN)

c = 100
vFil = 5 'Id de la fila (registro) a seleccionar

For b = 0 To c
If b + 1 = vFil Then
For i = 0 To UBound(mDatos())
oCeldaRegistro = oHojaBD.getCellByPosition(i,vFil)
mDatos(i) = oCeldaRegistro.getString()
Next
End If
Next

End Sub

Primero accedemos a la hoja (oHojaBD = ThisComponent.getSheets().getByName(vHoja)) identificada previamente en la variable (vHoja = "Datos") y después redimensionamos la matriz (ReDim mDatos(vN)) en función del número de campos del registro (vN = 7) y terminamos identificando el Id el registro deseado (vFil = 5).

Después ejecutamos la estructura ciclo-Condicional-ciclo que nos va a permitir acceder al contenido de los campos de ese registro. Para ello recorremos la base de datos (For b = 0 To c) hasta encontrar el valor del contador b que coincida con el Id del registro buscado (vFil) estableciendo en ese momento la condicionalidad (If b + 1 = vFil Then) dentro de la cual ejecutamos un segundo bucle (For i = 0 To UBound(mDatos())) que recorre las columnas (oCeldaRegistro = oHojaBD.getCellByPosition(i,vFil)) asignando a la matriz el contenido de las celdas correspondientes a ese registro (mDatos(i) = oCeldaRegistro.getString()).

También aquí el script deberá repetirse tantas veces como registros deseemos capturar, por lo que puede volverse poco manejable. En ese caso también será conveniente reconvertir el script en una función (ver nota 4).

Documento. Desde [este enlace] puedes acceder al documento que contiene ambos script.

NOTAS

(1) El trabajo con estos lenguajes se verá en próximas entradas, pero por ahora me voy a limitar a trabajar con OOo Basic.
(2) Aunque la base es el docap para crear una tabla expuesto en [esta entrada], el código que contiene los procedimientos de acceso se ubica en un módulo específico (ModAccesoDatos), diferentes del módulo que contiene los script y subrutinas del docap original (renombrado ahora como ModCrearTabla). Al código anterior no haré mención en la entrada actual.
(3) La forma en que se plantea este subproceso deriva directamente del modelo de trabajo empleado (trabajo en OOo Basic directamente desde el IDE) , incluyendo el uso de un comentario que clarifica cómo se debe proceder. De prestar una mínima atención al modo clásico de trabajo deberíamos crear un formulario de entrada de datos o recurrir a la función InputBox(), pero ahora eso es innecesario, ya que se espera que el usuario introduzca los datos directamente en el script.
(4) Solución que aplicaremos cuando llegue el momento. Por ahora es suficiente con exponer cómo acceder a los datos de una columna | variable | campo y te aconsejo que practiques este procedimiento hasta tener seguridad en su manejo.


jueves, 19 de diciembre de 2024

Análisis. Datos.

Tabla de datos basada en LibreOffice

Me propongo desarrollar en esta entrada un modelo de docap basado en Calc y OOo Basic que facilite la recogida sistemática de aquellos datos que, por el planteamiento del problema, no está contemplada en los modelos de docap anteriores. Remitiéndome a una [entrada previa], estaría hablando aquí de los datos necesarios para el análisis de datos no contemplado como análisis de resultados de un test. Esto equivale a decir, datos para análisis de las actuaciones, del funcionamiento del SEO o del funcionamiento de una prueba no contemplada en el capítulo de evaluación de pruebas.


Este docap tiene como único objetivo facilitar la recogida de datos para construir la tabla que será posteriormente objeto de análisis; no implica nada respecto al análisis propiamente dicho, procedimiento que tiene su propia lógica y sus fases. Como docap es realmente muy simple, ya que resulta de "recortar" el docap previamente definido como docap-modelo de evaluación, que [se expuso aquí].

Consta de dos fases: la creación del formulario (incluyendo la asociación de los controles a celdas) y de la tabla-base, y el desarrollo del código.

Para la creación del formulario deberemos tener en cuenta la diferenciación entre los datos de identificación y los datos de resultados, definiéndose los campos como variables o columnas de la base de datos. Se mantiene aquí la misma diferenciación que vimos en el docap de evaluación, pero no la diferenciación en la asignación de cada categoría a una columna de celdas, ya que en este docap no se plantea realizar ningún procesamiento de los datos, únicamente recogerlos. Esto tiene implicaciones en cuanto a simplificación del código, como veremos posteriormente. También lo tiene en el propio planteamiento del formulario, ya que no es necesario recoger nada que pueda ser calculado a posteriori, correspondiendo estos cálculo al propio proceso de análisis.


La imagen anterior representa lo que podría ser una formulación básica del formulario. En él se representa un sistema simple de recogida de datos de un conjunto de actuaciones identificadas por tipología (que deberá establecerse a priori) por OE (podría plantearse otro docap similar para cada perfil del SEO), centro y mes (1).

En la segunda fase (código) se diferencian dos script principales: Main y BorrarDatos. Este segundo permite reutilizar el docap sin necesidad de borrar manualmente los contenidos previamente introducidos (2). En esta entrada me centraré en explicar Main y su subrutina asociada (PasarDatos()), la cual se ocupa de pasar los datos a la tabla.

Empezaremos por establecer las variables necesarias...

Dim oHoja As Object, oCelda As Object
Dim mDatos() As Variant
Dim i As Integer, ni As Integer

... y por acceder a la hoja que contiene los datos.

oHoja = ThisComponent.getSheets().getByName("Form")

Después, dimensionamos la matriz mDatos() (ReDim mDatos(ni)) atendiendo al número de campos del formulario (ni = 6) que es el mismo que celdas I contienen datos. También definimos los contenidos de la matriz que identifica esas celdas (mCeldasi= Array("I1","I2","I3","I4","I5","I6","I7")).

Una vez establecidos estos valores, mediante un bucle recorremos esas celdas y asignamos su contenido a los elementos de la matriz mDatos(), siguiendo el procedimiento ya conocido.

For i = 0 To UBound(mDatos())
oCelda = oHoja.getCellRangeByName(mCeldasi(i))
mDatos(i) = oCelda.getString()
Next
 
Usamos esa matriz como parámetro que pasamos a la subrutina PasarDatos() para que ésta resuelva el proceso de pasar los datos a la tabla (3).

Sub PasarDatos(Datos() As Variant)

Dim oHojaBD As Object, oCeldaInicio As Object, oCeldaNuevoRegistro As Object, oCeldaId As Object
Dim i As Integer, a As Integer, b As Integer, c As Integer, d As Integer

oHojaBD = ThisComponent.getSheets().getByName("Datos")

c = 1000

For i = 0 To c
oCeldaInicio = oHojaBD.getCellRangeByName("A" & CStr(i+2))
If oCeldaInicio.getValue() = 0 Then
a = i + 2
d = i + 1
Exit For
End If
Next

MsgBox "Id de la nueva entrada: " & d

'Escritura de los datos en el registro vacío

oCeldaId = oHojaBD.getCellRangeByName("A" & CStr(a))
oCeldaId.setValue(d)

For b = 0 To UBound(Datos())
oCeldaNuevoRegistro = oHojaBD.getCellByPosition(b+1,a-1)
oCeldaNuevoRegistro.setString(Datos(b))
Next

End Sub

Documento. Puedes descargar el archivo [desde este enlace]

NOTAS

(1) Se suponen identificados a priori SEO y curso, aunque éste se identifica en el encabezamiento. Y todo ello en un contexto que define el objetivo del análisis al que servirá esta base de datos como de análisis de una tipología dada de actuaciones en un curso. Este planteamiento encaja con el supuesto de análisis de actuaciones del SEO. La base de datos (hoja Datos) contiene como encabezados (campos) los mismos conceptos.
(2) Dada su simplicidad y el carácter complementario que tiene respecto al objetivo del docap, me limito a remitirte al código de este script, que puedes encontrar en su IDE.
(3) Aunque el código de esta subrutina es fundamental para este docap, ya ha sido explicado en [esta entrada], en su parte final.

miércoles, 18 de diciembre de 2024

Evaluación. Modelo básico

Modelo básico de docap

Calificar de básico a este modelo equivale a decir concreción del tipo 2 visto en [esta entrada] y decir docap incide en lo mismo, especificando que nos vamos a limitar en la presente al uso de OOo Basic como lenguaje de referencia.


Me limito a desarrollar como docap-modelo el de tipo 2 por ser el de uso más frecuente en el posible desarrollo de herramientas de evaluación y también por facilitar el desarrollo de una alternativa en Python, así como por similitud con el soporte básico de recogida de datos en la sección Análisis. Esto no quiere decir que no vaya a crear docap de tipo 1, pero como éstos están ligados estrechamente al contenido de aprendizaje, no parece conveniente extenderse en estos momentos en explicar algo que va a sufrir tantas modificaciones como deriven de los contenidos concretos con los que se vaya a trabajar.

Entrando ya en materia y completando lo indicado en la entrada citada al inicio de ésta, el docap-modelo que presento ahora no se diferencia demasiado del presentado [en esta entrada] respecto al trabajo con documentos. Salvando algunas diferencias (que puede haberlas), la mayor y más significativa es que el docap actual añade una base de datos a los componentes de ese docap; el resto de componentes no tiene por qué ser diferente, aunque sí lo será el procedimiento de análisis. No obstante, como este proceso no será desarrollado en esta explicación por los mismos motivos (más otros añadidos) (1) que en aquel caso, las diferencias reales entre ambos en términos de programación se reducen significativamente. Por estos motivos, no resultará demasiado complicado explicar y entender lo que a continuación voy a exponer: en gran medida será suficiente con repetir lo ya visto.

Para concretar un docap-modelo de evaluación se debe primero especificar en que nivel se recoge la información, ya que es posible diferenciar tres: respuestas del alumno, puntuaciones obtenidas por ítem y puntuación total de la actividad o prueba. Considero que para que resulte realmente operativo (en términos de automatización) se debe contemplar como nivel básico al menos el segundo, pero en función de la característica del docap, desarrollaré el primer nivel: recogida de las respuestas, que implica que la puntuación de las mismas se resuelve mediante código.

Una segunda cuestión, aunque afecta al análisis y dije antes que en esto no iba a entrar, es con qué referentes se van a calificar los resultados. Esta cuestión requiere aclarar previamente qué tipo de evaluación se propone: normativa, criterial o mixta. Estas cuestiones no corresponden a esta entrada, ya que es necesario clarificar el significado concreto de esos conceptos y sus implicaciones, por eso opto aquí por una solución de compromiso muy simplificada, como corresponde a un docap básico (2).

La primera fase del proyecto es, como en los modelos documentales, el trabajo con la hoja de cálculo, empezando por la creación del formulario (hoja Form) (3). Este podría ser el resultado:


No he pretendido hacer nada sofisticado, por lo que es mejorable, pero sirve para el objetivo de este docap-básico. Contiene algunas diferencias con el docap-básico de trabajo con documentos, que explico a continuación:
  • En el formulario se diferencian tres partes, frente a las dos de los modelos anteriores: a parte de los botones de comando, los controles de entrada se diferencian por su función: los tres primeros son para datos de identificación y los restantes para la recogida de las respuestas de los ítem.
  • Esta diferencia se reproduce en la asignación de esos controles a celdas: los controles de datos de identificación se asocian a tres celdas de la columna I, y los de los controles de ítem a siete celdas de la columna J.
  • Esta diferenciación por columnas puede no ser necesaria, pero sí va a serlo la diferenciación en el acceso a cada uno de los dos tipos de datos, como veremos en el código correspondiente. Después explicaré el motivo.
Además, y esa es la principal diferencia con los docap-modelo documentales, deberemos crear una segunda hoja (Datos) donde crearé la tabla de datos, concretamente los encabezamientos de las columnas que sirven para identificar sus campos.

Una vez preparado el documento Calc, corresponde desarrollar el código que nos permita acceder a los datos introducidos por el usuario. Para ello declaro las variables (4) y accedo al documento y a la hoja Form, siguiendo el procedimiento ya conocido...

Dim oHoja As Object, oCelda As Object
Dim mDatosper() As String, mCeldasi() As String
Dim mResp() As String, mCeldasj () As String, mAcierto() As String
Dim mPtos() As Integer, PD As Integer
Dim n1 As Integer, nj As Integer
Dim i As Integer
Dim Md As Single, Dt As Single, Pz As Single, Pc As Single
Dim mDatos() As Variant

oHoja = ThisComponent.getSheets().getByName("Form")

Observa que he declarado dos matrices para contener los datos (mDatosper() y mResp()), ya que me interesa trabajar de forma independiente con cada uno de los dos bloques de datos. Como dije antes, esta diferenciación obedece al diferente tratamiento que tendrán los datos de identificación y los datos de respuestas a los ítem: los primeros no presentan novedad respecto a otras ocasiones, pero los segundos sí: sobre ellos realizaremos operaciones específicas, como tendremos ocasión de ver en su momento. Repito: con los datos personales nos limitamos acceder a ellos, pero los datos de respuesta a los ítem serán usados para generar los elementos de la tercera matriz (mPtos()), que contendrá las puntuaciones de los ítem resultante de la corrección de la prueba, las cuales están contenidas en una cuarta matriz (mAcierto()).

Por ello, el acceso a las respuestas del alumno, con la que finaliza la primera fase del script principal Main se resuelve de forma similar a cómo se resolvió el acceso a los datos personales, esto es: mediante un bucle For (For i = 0 To UBound(mResp()))

ni = 2
ReDim mDatosper(ni)
mCeldasi= Array("I1","I2","I3")

For i = 0 To UBound(mDatosper())
oCelda = oHoja.getCellRangeByName(mCeldasi(i))
mDatosper(i) = oCelda.getString()
Next

A continuación entramos en la segunda fase de Main, en la que nos corregimos las respuestas dadas por el alumno. En este caso el sistema es muy sencillo, pero puede ser mucho más laborioso, lo que hace recomendable la diferenciación que antes expliqué entre datos de identificación y datos de respuestas. 

Para realizar esta corrección damos contenido a la matriz que contiene las respuestas correctas (mAcierto() = Array("A","B","C","D","E","F","G")) y mediante un nuevo bucle (For i = 0 To  UBound(mResp())) y un condicional (If mResp(i) = mAcierto(i) Then) recorremos la matriz mResp() y comparamos su contenido con el esperado (mResp(i) = mAcierto(i)), asignando la puntuación que corresponde a cada ítem según se cumpla o no dicha condición. Aprovecho este mismo bucle  para calcular el sumatorio de las puntuaciones del alumno (PD = PD + mPtos(i)), lo que supone entrar en la tercera fase del script.

mAcierto() = Array("A","B","C","D","E","F","G")

For i = 0 To  UBound(mResp())
If mResp(i) = mAcierto(i) Then
mPtos(i) = 1
Else
mPtos(i) = 0
End If
PD = PD + mPtos(i)
Next

Como dije, este sumatorio constituye el inicio de la tercera fase, la cual está dedicada a realizar los cálculos que nos permitirán calificar los resultados que obtiene el alumno: el porcentaje de aciertos, en función del tamaño del test y su puntuación directa (Pc = (PD/(nj+1))*100), y la puntuación típica (Pz = (PD - Md)/Dt), en función de supuestos valores de media (Md = 5.38) y la desviación típica (Dt = 0.47) (5).

A continuación se desarrolla la cuarta fase del script principal (Main) que tiene como objetivo construir con los datos parciales anteriores una matriz única de datos con la que trabajaremos en las subrutinas a las que se llama al final de Main (PasarDatos(mDatos()) y Info(mDatos())) (quinta fase). 

Aunque podríamos ahorrarnos esta cuarta fase, por los motivos que dije en su momento puede no ser la mejor opción; pero, en todo caso, sí es conveniente crear una matriz única con todos los datos necesarios, ya que será muy útil para el correcto funcionamiento de las subrutinas que veremos más adelante. 

Para crear esta matriz-síntesis recurriré de nuevo a los bucles como medio para automatizar la asignación de contenidos, aunque algunos de ellos es necesario asignarlos de forma directa, como se puede observar en el código:

ReDim mDatos(19)

For i = 0 To UBound (mDatosper())
mDatos(i) = mDatosper(i)
Next

For i = 0 To UBound( mResp())
mDatos(i+3) = mResp(i)
Next

For i = 0 To UBound(mPtos())
mDatos(i+10) = mPtos(i)
Next

mDatos(17) = PD
mDatos(18) = Pc
mDatos(19) = Pz

Primero redimensionamos la matriz-resumen (ReDim mDatos(19)) y después vamos asignando los datos, empezando por los personales (mDatos(i) = mDatosper(i)), siguiendo por las respuestas del alumno (mDatos(i+3) = mResp(i)), después los resultantes de la corrección de la prueba (mDatos(i+10) = mPtos(i)), finalizando con los datos-resumen, resultantes de los cálculos (vg. mDatos(17) = PD) (6).

Paso ahora a explicar los dos procedimientos que restan y que se ejecutan de forma independiente del script principal, mediante subrutinas: 
  • La primera (Sub PasarDatos(Datos() As Variant)) está pensada para construir la base de datos, trasladando los datos de la matriz-resumen (mDatos()) a la tabla creada en la hoja Datos.
  • La segunda (Sub Info(Datos() As Variant)), ya conocida de [esta entrada], para crear el informe.
Estas subrutinas constituyen una especie de "externalización de procedimientos" del algoritmo, pero esto, además de ejemplificar el uso del principio de la programación modular, resulta necesario más que meramente conveniente, dada la relativa complejidad de ambos subprocesos y su independencia respecto al objetivo principal del script Main

Dado que la segunda ya es conocida desde la publicación de [esta entrada], me limitaré a explicar la primera. Con ella automatizamos la creación de la tabla o base de datos, dotando a nuestro docap de un potencial muy interesante: recopilar los resultados de las aplicaciones individuales de una prueba, lo que facilita el posterior análisis de dichos datos (7).

Sub PasarDatos(Datos() As Variant)

Dim oHojaBD As Object, oCeldaInicio As Object, oCeldaNuevoRegistro As Object, oCeldaId As Object
Dim i As Integer, a As Integer, b As Integer, c As Integer, d As Integer

oHojaBD = ThisComponent.getSheets().getByName("Datos")

c = 1000

For i = 0 To c
oCeldaInicio = oHojaBD.getCellRangeByName("A" & CStr(i+2))
If oCeldaInicio.getValue() = 0 Then
a = i + 2
d = i + 1
Exit For
End If
Next

MsgBox "Id de la nueva entrada: " & d

'Escritura de los datos en el registro vacío

oCeldaId = oHojaBD.getCellRangeByName("A" & CStr(a))
oCeldaId.setValue(d)

For b = 0 To UBound(Datos())
oCeldaNuevoRegistro = oHojaBD.getCellByPosition(b+1,a-1)
oCeldaNuevoRegistro.setString(Datos(b))
Next

End Sub

Esta subrutina cuenta con un argumento que remite a la matriz-resumen (PasarDatos(Datos() As Variant)de los datos del script principal Main, resultando ahora evidente la necesidad de su creación: gracias a ellos se simplifica la escritura de esos datos en la tabla.

Pero antes de dar ese paso debemos acceder a la hoja Datos (oHojaBD = ThisComponent.getSheets().getByName("Datos")) y posicionarnos adecuadamente en la primera fila no escrita (8) mediante un bucle condicionado

For i = 0 To c
oCeldaInicio = oHojaBD.getCellRangeByName("A" & CStr(i+2))
If oCeldaInicio.getValue() = 0 Then
a = i + 2
d = i + 1
Exit For
End If
Next

Cuando encontramos la celda de la columna A (oCeldaInicio = oHojaBD.getCellRangeByName("A" & CStr(i+2))) que cumple la condición (oCeldaInicio.getValue() = 0) asignamos el valor de inicio a la variable-contador que nos van a permitir recorrer las diferentes columnas de la tabla (a = i + 2) y de captura el valor Id de la fila A que precede a la que no cumple el criterio del condicional (d = i + 1). Después interrumpimos el bucle.

Después volvemos a posicionarnos en la celda de la columna A localizada mediante el bucle anterior (oCeldaId = oHojaBD.getCellRangeByName("A" & CStr(a))) y escribimos en ella el valor Id que corresponde (oCeldaId.setValue(d)) y de nuevo, mediante un bucle dimensionado en base a la matriz-resumen (For b = 0 To UBound(Datos())), recorremos ahora las columnas (campos) que se corresponden con la fila (oCeldaNuevoRegistro = oHojaBD.getCellByPosition(b+1,a-1)) y trasladamos a cada celda los datos de la matriz (oCeldaNuevoRegistro.setString(Datos(b))).


NOTAS

(1) Si en el caso de los docap-documento el tema del procesamiento remitía a las dificultades para tratar de forma simplificada el problema del procesamiento, en el caso de los docap-evaluación la problemática se acentúa, ya que de una prueba de evaluación se derivan muchos más datos cuantitativos y de categorización que requieren procesos de análisis de la bifurcación de mayor complejidad. No obstante el tratamiento de la opcionalidad en el caso de los contenidos textuales es realmente más complejo, ya que se ajustan en menor medida que los datos de un test a un planteamiento de tipo algorítmico.
(2) Por lo que no resulta satisfactorio desde ninguna de las perspectiva de análisis, pero tiene la ventaja de incluir referencias a ambos modelos (y al mixto). Además, y es lo que importa, evidencia los mecanismos básicos del análisis desde ambas perspectivas (la normativa y la criterial), aunque de forma muy simplificada.
(3) Dado que el informe se crea sobre un documento en blanco, no es necesario crearlo. También es posible utilizar un documento previamente creado y formateado, con lo que implica de trabajo previo y de modificación del código de acceso. Ver al respecto el modelo básico 2 de docap de documento en [esta entrada]
(4) Incluyendo las que serán necesarias para el procesamiento posterior y que explicaré en su momento.
(5) Ambos valores sirve únicamente a efectos del funcionamiento del docap, pero no reflejan valores reales de ninguna prueba en concreto.
(6) Puedes apreciar que los tres primeros bloques se ejecutan mediante bucles (reajustando los valores de los índices de la matriz-resumen) y en los últimos la asignación se realiza de forma directa.
(7) Este análisis es anticipatorio pero diferentes del que nos planteamos en la sección Análisis del Blog. También lo es este docap del que podemos crear dentro de ese campo de trabajo.
(8) Recuerda que una fila de una tabla equivale a un registro en una base de datos. Las columnas de la tabla equivalen a los campos de la base de datos y serán tratadas como variables en el análisis de datos.

lunes, 16 de diciembre de 2024

Documentos. Modelo básico

Modelo básico de docap (b)

Hay una diferencia entre este segundo modelo y [el anterior] en la fase de preparación, además de la que deriva de la forma diferente de manejar el servicio Writer: dado que ahora trabajamos con un documento creado (frente al documento en blanco del modelo anterior), no sólo tenemos que acceder a dicho documento (y no sólo activar el servicio Writer), sino que también tenemos que tener preparado previamente ese documento. Todo esto supone que este segundo modelo de docap resulte sensiblemente más complejo (1).


Para empezar crearé el soporte Calc y el documento Writer, que ubicaré en un subdirectorio de fácil acceso (2). En mi caso creo un subdirectorio en una unidad USB-unidad D, al que llamo Acta (3) e incluyo en ella dos documentos: GestorActa (Calc) y ActaModelo (Writer).

El segundo paso consiste en crear el formulario en GestorActa y asociar los controles a las celdas, igual que en el docap-modelo anterior. También creo los módulos Modulo1 Modulo2 desde el IDE para contener los script que crearé más tarde.

Aunque puedo seguir trabajando en el script Main con las instrucciones que darán acceso a las celdas que contienen los datos, opto por trabajar sobre el documento-modelo, formateándolo y generando las posiciones de los marcadores. Este podría ser el resultado:


Sobre este documento, y a modo de ejemplo, posiciono tres marcadores (Insertar | Marcador) (4) que llamados respectivamente mcdAsisten, mcdTemas y mcdFecha donde se infiere por su nombre. Finalizo la preparación del documento guardándolo y creando una copia de seguridad (5).

Una vez que hemos creado el documento-modelo,  volvemos a trabajar sobre GestorActa para generar el código OOo Basic. En primer lugar, desarrollo el procedimiento de acceso al contenido introducido por el usuario mediante el formulario (6). Incluyo la declaración de variables y matrices (algunas las explicaré más adelante):

'Variables
Dim oHoja As Object, oCelda As Object
Dim mDatos() As String, mCeldas() As String, mMarcadores() As String
Dim n As Integer, i As Integer
Dim sAsisten As String

'Acceso a datos

oHoja = ThisComponent.getSheets().getByName("Hoja1")

n = 5
ReDim mDatos(n)

mCeldas= Array("I1","I2","I3","I4","I5","I6")

mMarcadores() = Array("mcdAsisten","mcdTemas","mcdFecha")

For i = 0 To UBound(mDatos())
oCelda = oHoja.getCellRangeByName(mCeldas(i))
mDatos(i) = oCelda.getString()
Next

Primero accedo a la Hoja1 del documento Calc (oHoja = ThisComponent.getSheets().getByName("Hoja1")) y después de redimensionar la matriz mDatos (ReDim mDatos(n)) en función del número de elementos de la lista (n = 5), cargo el contenido en dicha matriz mediante un ciclo For (For i = 0 To UBound(mDatos())) según el procedimiento ya conocido.

oCelda = oHoja.getCellRangeByName(mCeldas(i))
mDatos(i) = oCelda.getString()

Previamente he dado contenido a la matriz mCeldas() (mCeldas= Array("I1","I2","I3","I4","I5","I6")) para facilitar el funcionamiento del bucle (oCelda = oHoja.getCellRangeByName(mCeldas(i))). De este modo los datos contenidos en las citadas celdas pasan a la matriz mDatos() y quedan disponibles para su manejo desde el código (mDatos(i) = oCelda.getString()).

Además adelanto trabajo dando contenido a la matriz mMarcadores() (mMarcadores() = Array("mcdAsisten","mcdTemas","mcdFecha")) que necesitaré más adelante.

Dado que en este docap también inciden los mismos condicionantes que en el docap anterior, tampoco aquí me detendré en desarrollar la fase procesamiento más allá del mínimo que exige la diferenciación de los tres campos que identificados por asociación con los tres marcadores implementados en el documento. Este mínimo se concreta en el bucle que asigna a la variable sAsisten la concatenación de los cuatro primeros componentes de la matriz mDatos() (sAsisten = sAsisten & Chr(13) & mDatos(i)) que corresponden a los posibles asistentes a la reunión:

For i = 0 To 3
If mDatos(i) = "" Then
sAsisten = sAsisten
Else
sAsisten = sAsisten & Chr(13) & mDatos(i)
End If
Next

En él, el condicional anidado If (If mDatos(i) = "" Then) impide que se generen saltos de línea en caso de ausencia de alguno de los cuatro potenciales asistentes (Asisten = sAsisten). 

Además incluyo en Main el código necesario para acceder al documento-modelo...

Dim sRutaAcceso As String, sRutaGuardar As String
Dim mOpciones(1) As New "com.sun.star.beans.PropertyValue"
mOpciones(0).Name = "AsTemplate"
mOpciones(0).Value = True

Dim oDocModelo As Object
sRutaAcceso = ConvertToUrl("D:/ACTA/ActaModelo.odt")
oDocModelo = StarDesktop.loadComponentFromURL( sRutaAcceso, "_blank", 0, mOpciones())

... según el procedimiento ya explicado en [esta entrada] en la que se trata este tema con más detalle. Como característica específica decir que en este caso abrimos dicho documento en modo plantilla

Dim mOpciones(1) As New "com.sun.star.beans.PropertyValue"
mOpciones(0).Name = "AsTemplate"
mOpciones(0).Value = True

... lo que facilita mantener sin modificar el propio documento-modelo, evitando así posibles errores en el manejo de dicho documento, como puede ser el borrado accidental de los marcadores o la sustitución de dicho documento por el que deriva del uso del docap, a consecuencia de una grabación descuidada del documento resultante.

Finalizamos Main con la llamada a la subrutina de escritura del acta, tantas veces como marcadores están disponible.

Acta(oDocModelo,sAsisten,mMarcadores(0))
Acta(oDocModelo,mDatos(4),mMarcadores(1))
Acta(oDocModelo,mDatos(5),mMarcadores(2))

Esta subrutina se limita a ejecutar la escritura del contenido pasado mediante el parámetro Texto (por ejemplo, sAsisten), en la posición que indica el parámetro Marcador (por ejemplo mMarcadores(0)), habiendo previamente accedido al documento abierto (oDocModelo = StarDesktop.loadComponentFromURL( sRutaAcceso, "_blank", 0, mOpciones())), paro lo cual debemos pasar también como parámetro (Modelo As Object) dicho modelo (oDocModelo) (7)

Sub Acta(Modelo As Object,Texto As String,Marcador As String)
Dim oMarcador As Object
oMarcador = Modelo.getBookmarks().getByName(Marcador)
oMarcador.getAnchor.setString(Texto)
End Sub

Documento. Desde sus enlaces puedes descargar [GestorActa] y [ActaModelo]. Recuerda que debes guardarlos en una unidad externa (D) dentro de un subdirectorio (ACTA). Si optas por otra ubicación deberás modificar la instrucción sRutaAcceso = ConvertToUrl("D:/ACTA/ActaModelo.odt") para ajustarla a la nueva ubicación.

NOTAS

(1) De lo que se deriva una recomendación: si no es estrictamente necesario, siempre será preferible trabajar con el modelo de docap más simple.
(2) Aunque es opcional, yo te recomiendo trabajar en estos casos con un soporte de almacenamiento externo. Por motivo de seguridad y confidencialidad así lo hago yo en este ejemplo, dado que quedará publicado en la nube. De este modo también te será más sencillo replicar el funcionamiento de este docap: es suficiente con seguir los mismos pasos que te explico en el texto.
(3) Como puede comprender, en este caso no es posible crear un docap totalmente independiente de un objetivo concreto de trabajo, aunque éste puede ser el que tu necesites. Por ello deberemos trabajar sobre un supuesto. Este supuesto es, en este caso, la cumplimentación de determinados apartados de un acta, por ejemplo de la UO. No por ello deja doporte para el objetivo de semi-automatizar la cumplimentación del acta.
(4) En [esta entrada] se explica con más detalle cómo proceder. 
(5) Esta copia garantiza disponer del documento tal y como fue creado, pensando en que pueda surgir alguna dificultad en el proceso, dada la sensibilidad de los marcadores al borrado accidental. Después veremos que nos conviene trabajar con una plantilla del documento para que el original siempre esté disponible.
(6) El mismo código que creamos en el docap-modelo anterior, como podrás comprobar.
(7) Esta no es la única forma de desarrollar ambos subprocesos, pero tiene la ventaja de diferenciar la fase de acceso al documento-modelo de la escritura del contenido propiamente dicha. Otra opción podría haber sido diferenciar la fase de acceso y manejo de los datos (Main) de la de acceso al documento y escritura. Una tercera fórmula sería diferenciar esa subrutina en dos partes: la primera se encargaría de acceder al documento-modelo y la segunda de escribir en él.