Access vs MySQL

AccessMySQL

 

Access vive. No solo vive, sino que, además, goza de una estupenda salud. Quienes piensen que internet convierte Access en algo anticuado, no conocen Access o no acaban de entender el sentido del cambio de los tiempos.

Nadie va a trabajar en la oficina con una aplicación de gestión en una tablet o un smarphone, sino que seguirá trabajando con un PC y, de manera complementaria, usará la tablet, o mejor una Surface o equivalente, cuando necesite movilidad. En aplicaciones de escritorio, Access sigue sin competencia como herramienta de desarrollo rápido de aplicaciones, Access 2013 facilita la creación de aplicaciones web y la eclosión de los servidores de datos en la nube (Azure, Amazon, Google) libra al desarrollador de Access, a menudo freelance, de los costes, en tiempo y en dinero, del mantenimiento de la infraestructura necesaria, al tiempo que le garantiza que su aplicación puede acceder a sus datos desde cualquier parte del mundo.

En el Grupo Virtual de Usuarios Access España estamos charlando de todo esto, entre otras muchas cosas, los terceros jueves de mes a las 21:00 hora de España. El último jueves, Juan Soto nos explicó la mejor forma de sacarle partido a los procedimientos almacenados de SQL Server desde Access y el mes anterior Ángel Gil nos contó cómo conectar Access con SQL Azure, que él usa con tablets windows. Cada mes un ponente de relumbrón, o no tan conocido pero con mucho fundamento, presenta un tema interesante y luego charlamos sobre ello o sobre cualquier cosa que se nos ocurra.

Esta semana, el próximo jueves 20 de noviembre, a las 21:00 hablaremos de Access con MySQL. César Augusto Cardona, CEAC, un viejo conocido en los foros, nos hablará de cómo trabajar con Access y MySQL en la red.

En esta ocasión César dará respuesta, entre otros, a los siguientes puntos destacados:

– ¿Por qué MySql?
– Creación de Tablas MySql
– Conversión de Access a MySql
– ODBC de conexión
– Configuración ODBC para distribución automática
– Consultas Access y MySql

César es Licenciado en Tecnología e Informática. Ha trabajado como “freelancer” y también en PwC (Price Waterhouse Coopers). Actualmente es funcionario público de la Secretaría de Educación Municipal. Dispone de canal propio en YouTube: https://www.youtube.com/channel/UC070O4HA_eUa4PeeFJu2YdA

Nuestra reunión será usando el siguiente enlace:

https://join.me/accessusergroups

Puedes usar cualquier navegador para ver y escuchar la reunión. Si estas usando un teléfono o tableta usa la aplicación join.me app y usa el código de reunión: 842-599-774842-599-774

Para escuchar la charla: Marca el número de teléfono:

United States ++1.213.226.1066+1.213.226.1066
España +34.81.061.0479
Access Code 842-599-774842-599-774#

Números internacionales disponibles

Por computadora vía Internet: Únete a la reunión usando el enlace https://join.me/accessusergroups . Una vez en la reunión presiona el icono de teléfono y selecciona ‘Call vía Internet’ (se puede necesitar una pequeña descarga).

Publicado por Chea con no comments

Access en la nube. Pon tus datos en SQL Azure.

imagesazure

¿Qué puede haber mejor que una aplicación de escritorio hecho en Access con los datos en internet? Empezar usando Access contra SQL Azure, utilizando ODBC, es una forma muy sencilla de dar los primeros pasos sin que se comprometan futuras mejoras que garanticen la escalabilidad

El próximo jueves, día 18 de septiembre, en el grupo virtual de usuarios Access España Ángel Gil, Conrrad69 en los foros, que trabaja habitualmente con esa combinación, nos explicará paso a paso y con todo detalle cómo crear una base de datos en SQL Azure y cómo vincularla desde Access con ODBC. No es la solución definitiva, no es así como trabaja Ángel, pues hay que trabajarlo bastante mas para conseguir una seguridad apropiada, mejorar el rendimiento o solucionar qué pasa cuando no hay conexión a internet, pero sí que es la mejor manera de empezar y resulta muy buena para cosas sencillas.

Ángel cuenta con 20 años de experiencia en el desarrollo de sistemas de gestión empresarial, utilizando tecnologías varias como FileMaker, MS Access, VBA y SQL Azure, combinando además esta experiencia profesional con actividades de formación presencial y on-line. Comparte sus conocimientos a través de su página http://www.accessyexcel.com, y colabora en otras webs como http://www.docebit.com y http://www.louesfera.com. Y, para un mayor abundamiento, también podéis encontrarlo en YouTube (http://m.youtube.com/user/angilmen).

Últimamente está desarrollando aplicaciones de Access que corren en tablets con Windows 8 con conexión a internet. Algo realmente interesante que hace mas atractivo el tema de la próxima reunión.


No debemos confundir los grupos Access España y Access Latino. Son dos grupos distintos que se reúnen en días y con horarios distintos, ambos están abierto a quien quiera participar y puede hacerse en los dos.

Os esperamos el 18, a las 21 h, en http://accessusergroups.org/espana/meetings/. ¡Os aseguro que es para no perdérselo!

¡Nos vemos el jueves!

virtual-chapters-295x300

Publicado por Chea con no comments

Presentación sobre el BackStage de Access 2010 en AccessUserGroups España

LogoCaballoNckModerno

El próximo jueves, 21 de agosto de 2104 a las 21:00, tendrá lugar la reunión mensual del Grupo Virtual de Usuarios Access España. En ella,  Neckkito hará una presentación sobre el BackStage de Access 2010, qué es, cómo funciona y cómo modificarlo. Después tendremos un rato de charla sobre lo que se acaba de explicar, lo que queramos aportar o preguntar, o, simplemente, lo que nos apetezca.

Si Access te interesa, estás invitado a participar.

Publicado por Chea con no comments

El jueves, segunda reunión del grupo virtual de usuarios Access España

imagesCYY1731NEl próximo jueves, 18 de junio, a las 21:00 tendrá lugar la segunda reunión del grupo virtual de usuarios Access España.

Emilio Sancha, administrador del Foro de Access y VBA, que ha sido MVP en Access varios años, nos hará una presentación sobre automatización con Office. Nos contará cómo podemos preparar un proceso que, mediante automatización, nos permita extraer el código de un archivo de Office y exportarlo, formateado, a un documento de Word.

Para unirte a la reunión, sigue el siguiente enlace: http://accessusergroups.org/espana/meetings/. Se estará usando el enlace para todas las futuras reuniones del Access User Groups. Ahora utilizara el mismo ID para todas las reuniones de cada grupo

Únete a nosotros si Access te apasiona. Entre todos vamos a construir un grupo en el que reunirnos online para exponer temas interesantes, intercambiar código, experiencias y dudas sobre todo lo que tenga algo que ver con Access.

Publicado por Chea con no comments

Primera reunión del grupo virtual de usuarios Access España

Ya está convocada la primera reunión del grupo virtual de usuarios Access España.

bandera

Únete a nosotros si Access te apasiona. Entre todos vamos a construir un grupo en el que reunirnos online para exponer temas interesantes, intercambiar código, experiencias y dudas sobre todo lo que tenga algo que ver con Access.

Publicado por Chea con no comments
Archivado en:

Grupos Virtuales de Usuarios de Access

La madrugada del miércoles al jueves he asistido,entusiasmado, a la primera reunión del Grupo Virtual de Usuarios Access Latino América, un capítulo de los Virtual Access User Groups que está impulsando Juan Soto. El propio Juan Soto hizo la presentación del grupo y nos estuvo hablando de las aplicaciones web de Access 2013 y la posibilidad de usarlas en aplicaciones híbridas accediendo a las tablas que crea en Azure desde una aplicación de escritorio. En el blog del grupo nos lo cuenta Juan y nos facilita la presentación que utilizó.

 

virtual-chapters-295x300

 

Yo quiero un sitio así para España, que el de América Latina coincide con la madrugada española. Eso le dije a Juan Soto y la respuesta es que contamos con todo el apoyo y la infraestructura de AccessUserGroups.org para hacerlo. Ya tenemos página reservada para el Grupo Access España y ya estamos moviéndonos, aquí en España, para formar un grupo de gente dispuesta a impulsarlo. Ya contamos con unos cuantos expertos dispuestos no solo a participar en el grupo, sino también a presentar algún tema.

De lo que se trata es de reunirse virtualmente una vez al mes a través de internet para charlar sobre Access, intercambiar experiencias y código y plantear dudas y soluciones. El formato será empezar por una presentación de algún tema interesante, que puede hacer un miembro del grupo o un experto ajeno a él, y luego abrir un coloquio en el que debatir sobre el tema expuesto, sugerir próximos temas y plantar problemas. Aunque no es definitivo, va cobrando fuerza la idea de que los días de reunión sean los terceros jueves de cada mes a las 19,00 y la primera reunión, que anunciaremos, será en mayo.

Ya tenemos expertos dispuestos a hacer presentaciones sobre el uso de la cinta de opciones (Ribbon), de los Store Procedures de SQL Server como origen de formularios Access, de las macros de datos, de la automatización y de Azure como back-end de aplicaciones de Access usadas en las nuevas tablets con Windows 8.1. También nos han sugerido temas que aún están a falta de expertos que nos hablen de ellas: uso de librerías .net desde Access, aplicaciones híbridas (espero convencer a Juan Soto) y MySQL con Access.

El grupo está abierto a todo el que sea un apasionado de Access. Si estás interesado en participar, si quieres exponer algún tema, o si quieres sugerir algún tema que tratar, la página está aún en construcción, pero el formulario de contacto ya lleva unos días funcionando: ponte en contacto con nosotros.

Publicado por Chea con 1 comment(s)

Utilizar un subinforme como sustituto de un control TreeView en Access 2010

Con los problemas y vulnerabilidades cada vez mas frecuentes de los Activex, aunque existan parches para ir tirando, muchos programadores en Access han decidido finiquitar el único control del vetusto Common Controls que seguía mereciendo la pena y que carecía de una alternativa nativa: el TreeView Control. El que mas y el que menos se ha elaborado sus propias soluciones recurriendo a lo que tenían a mano en Access, en general jugando con cuadros de lista o subformularios y el resultado, variable, a veces sorprende por lo sencillo y lo eficiente. Sin embargo, no conozco a nadie que haya optado por utilizar un informe.

En Access una estructura en árbol se muestra fácilmente, de toda la vida, con un informe agrupado. Puede ser como una fotografía, mas rica, de un Control TreeView, pero es difícil imaginarse un informe que no sea completamente estático, sin capacidad de contraer o expandir las ramas. Sin embargo las versiones 2007 y 2010 de Access aportaron nuevas características, la Vista Informe en 2007 y la posibilidad de incrustar un subinforme en un formulario en 2010 que, bien utilizadas, pueden convertirse en una alternativa, mas sencilla de usar, mas potente y mas bonita que el control original.

 

El subinforme como alternativa al TreeView Control

Desde Access 2007, existe una nueva vista para los informes, la llamada Vista Informe que, al contrario que los informes tradicionales, no está pensada para imprimir, sino para ver en pantalla; por eso no reparte el informe en páginas, por lo que tampoco se producen los eventos Format o Print, sino que lo muestra todo seguido, y, también por eso, admite búsquedas y filtros directamente o eventos mas propios de los formularios, como Click, DblClick, keyDown, etc.

Desde Access 2010, los subInformes, en Vista Informe, se pueden incluir en un formulario exactamente igual que si fueran subformularios. Pueden ser independientes, o vinculados al formulario principal a través de las propiedades LinkMasterFields y LinkChildFields.

Con todos estos ingredientes es fácil imaginar que un informe podría sustituir con ventaja a un control TreeView. Los informes podemos vincularlos a un origen de datos de forma muy sencilla y sin escribir una sola línea de código, y podemos agrupar datos con facilidad, mostrando una estructura en ramas. Podemos usar distintos formatos de texto, colores, fondos, formatos condicionales, añadir los campos que queramos, resúmenes, totales… solo con unos clics de ratón.

Parece que en todo, en facilidad y posibilidades, superan al control TreeView y que solo les falta una cosa: La facilidad de los controles TreeView para contraer y expandir ramas. Pues bien, esto también es posible con un poco de programación, mas bien muy poco, un poco de SQL y cuidando algunos detalles del diseño, nada que no esté al alcance la la inmensa mayoría de los desarrolladores en Access.

Aquí tenemos una imagen de un ejemplo de un subinforme ejerciendo de TreeView. Puede descargase la aplicación de ejemplo en http://www.bengoechea.net/utilidades-1/reporttreeview

image

 

 

¿Funciona? Sí, y solo un parpadeo casi imperceptible a la hora de contraer o expandir una rama hace que no sea cien veces mejor que un control TreeView.

Aunque hay que cuidar algunos detalles en el diseño convencional del informe para que el resultado sea el que buscamos, esta parte no se aleja de la manera de diseñar un informe con grupos a la que estamos acostumbrados. Lo que tiene su pequeño truco, que necesita mas explicación, es la manera de hacer que las ramas se expandan o contraigan.

La consulta origen del informe

Un informe con grupos se basa en una única consulta en la que están contenidas todas las tablas relacionadas entre sí por campos en común. Si a estos campos en común añadiéramos uno mas de cada lado, cambiando su valor podríamos hacer que la consulta no devolviera los registros correspondientes de esa rama, si hacemos, a continuación de cambiar el valor de ese campo, un requery del informe basado en esa consulta, el efecto será el de contraer o expandir la rama.

A ver si me explico un poco mejor. Tenemos dos tablas, Género y Especie, con un campo en común idGenero; con una consulta que vincule ambas tablas mediante ese campo el resultado será una relación de todos los géneros y las especies de cada género. Pero podemos incluir un campo mas en cada tabla, bExpandir y bExpandiendo, por ejemplo, y bExpandiendo tendría siempre valor True. Si relacionamos también ambas tablas por esos campos, cambiando el campo bExpandir de la tabla Género coincidirá o no con el de la tabla Especie y, en consecuencia, se mostrarán o no las especies de ese género.

Ese planteamiento inicial obligaría a a cambiar el diseño de las tablas implicadas y a andar cambiando datos en ellas de manera poco justificada, por lo que, partiendo de esa idea, lo que hacemos es utilizar una tabla auxiliar, con el campo en común y el campo bExpandir, que interponemos entre las dos tablas relacionadas. No necesitamos modificar ni andar tocando las tablas originales, sino que escribimos en la tabla auxiliar y, en vez utilizar una tabla auxiliar para cada par de tablas relacionadas, utilizamos una sola, con distintas consultas filtradas por el nombre de la tabla del lado uno, que son las que interponemos.

Así sería la consulta original del informe que mostramos arriba si fuera estático:

 

image

 

O, si lo prefiere, así sería el texto SQL:

SELECT Títulos.*, Grupos.*, cuentas.* 
FROM (Títulos LEFT JOIN Grupos ON Títulos.ID_TITULO = Grupos.ID_TITULO) LEFT JOIN cuentas ON Grupos.ID_GRUPO = cuentas.ID_GRUPO;

Sin embargo, para poder ocultar o mostrar las ramas a voluntad, la consulta qArbolCuentas ha quedado como ésta:


image

 

 
SELECT Títulos.*, qExpandirTitulo.NombreTablaPadre, qExpandirTitulo.Expandir 
AS ExpandirTitulo, Grupos.*, qExpandirGrupo.NombreTablaPadre, qExpandirGrupo.Expandir 
AS ExpandirGrupo, cuentas.* 
FROM (((Títulos LEFT JOIN qExpandirTitulo ON Títulos.ID_TITULO = qExpandirTitulo.idPadre) 
LEFT JOIN Grupos ON qExpandirTitulo.idPadre = Grupos.ID_TITULO) 
LEFT JOIN qExpandirGrupo ON Grupos.ID_GRUPO = qExpandirGrupo.idPadre) 
LEFT JOIN cuentas ON qExpandirGrupo.idPadre = cuentas.ID_GRUPO;

 

Es decir, entre Títulos y Grupos hemos interpuesto la consulta qExpandirTitulo, y, entre Grupos y Cuentas, qExpandirGrupo. También hemos añadido los respectivos campos Expandir, que cambiamos de nombre.

Las consultas qExpandirTitulo y aExpandirGrupo, se basan ambas en una única tabla filtrada porque Expandir sea Verdadero y por un nombre, que podría se aleatorio, y que servirá luego para filtrar en la tabla que registros modificar.


imageimage

SELECT tblExpandir.Id, tblExpandir.idPadre, tblExpandir.NombreTablaPadre, tblExpandir.Expandir 
FROM tblExpandir 
WHERE (((tblExpandir.NombreTablaPadre)="Grupos") AND ((tblExpandir.Expandir)=True));

 

SELECT tblExpandir.Id, tblExpandir.idPadre, tblExpandir.NombreTablaPadre, tblExpandir.Expandir 
FROM tblExpandir 
WHERE (((tblExpandir.NombreTablaPadre)="Títulos") AND ((tblExpandir.Expandir)=True)); 

Como la tabla tblExpandir está de momento vacía, ahora la consulta qArbolCuentas solo mostraría los Títulos, pues no existen los campo idPadre que se interponen entre las distintas tablas. Si le añadimos el ID de un título, lo identificamos con el nombre “Titulos” y le damos al campo Expandir valor Verdadero, se mostrarán todos los Grupos de ese Título, y si cambiamos Expandir a Falso, no se mostrarán, pues qExpandirTitulo solo devuelve los registros en que Expandir es verdadero.

Con las consultas creadas, solo nos falta crear el informe y añadirle un par de procedimientos para que busque y modifique el campo Expandir correspondiente y lo añada si no existe.

 

El diseño del informe

El diseño del informe es bien sencillo, aunque hay algunos detalles que no se deben dejar escapar.

image

 

Tiene su origen en la consulta qArbolCuentas, está agrupado por Titulo y por Grupo y muestra los campos Titulo, Grupo y Cuenta y, a la izquierda de los dos primeros, el valor correspondiente de Expandir y, a la derecha de cada uno de ellos, oculto, el ID correspondiente.

Debemos cuidar que las propiedad Autocomprimible, tanto de los controles que se expanden como de las secciones que los contienen, sea y, además, la propiedad Alto Automático de las distintas secciones (Encabezado de Grupo y Detalle) sea también .

Si los controles no se comprimen, o si no se comprime la sección que los contiene, aunque no haya contenido para ese grupo, se muestra en blanco el espacio correspondiente a ese control o a esa sección y, aparte del derroche de espacio innecesario, el resultado es menos estético.

Lo controles solo se comprimen cuando no tienen nada que mostrar y las secciones cuando todos los controles están comprimidos, así que no podemos añadir a las secciones prácticamente nada que no sean campos de texto que puedan estar vacíos; no podemos, por ejemplo, añadir casillas de verificación o botones. Tampoco podemos ocultarlos o cambiar de tamaño sobre la marcha, pues no tenemos los eventos Format y Prine

La pega que tenemos es que ExpandirGrupo y ExpandiTitulo, devuelven valores 0 y –1, aunque no haya registros que mostrar y que, además, no queremos mostrarlos así, sino como + y –.

En el campo ExpandirTitulo es mas sencillo, pues, como, al ser el tronco, siempre se mostrará, y no importa que no se comprima. Lo que hacemos con el campo ExpandirGrupo es cambiarlo por un campo calculado que devuelve un nulo si el Grupo es nulo.

=SiInm(EsNulo([Grupo]);Nulo;[ExpandirGrupo])

Y, en ambos campos, para que se muestren como + y –, lo que utilizamos es la propiedad Formato, que tiene distintas secciones, separadas por punto y coma, para valores positivos, negativos, ceros y nulos. En ambos casos, la propiedad formato es:

+;-;+

 

En el caso de la cuenta, no hay un campo Expandir, por lo que utilizamos un campo calculado. txtPunto, cuyo origen es:

=SiInm(EsNulo([Cuenta]);Nulo;0)

Y, como lo que queremos es que se muestre una viñeta, la propiedad Formato es:

●;●;●

Volviendo a ExpandirTitulo, y puesto que siempre muestra algún registro, no nos ha importado adornarlo un poco poniendo un botón de forma redonda y fondo transparente sobre él, para darle un resultado mas aparente image

 

El código VBA

Lo que básicamente hace el código es que, cuando pulsamos sobre el campo ExpandirTitulo o ExpandirGrupo, que en el informe se muestran con + o, se cambia por el contrario el valor correspondiente en la tabla tblExpandir para la tabla Titulos o Grupos y, si no existía el registro en la tabla tblExpandir, se añade. A continuación, hacemos un Requery del informe, que será lo que produzca el efecto de contraer o expandir la rama.

Private Sub ExpandirTitulo_Click()
fFindEditTblexpandir Nz(Me.Títulos_ID_TITULO), "Títulos", Not Nz(Me.ExpandirTitulo)
Echo False
Me.Requery
Echo True
End Sub
Private Sub TxtExpandirGrupo_Click()
fFindEditTblexpandir Nz(Me.Grupos_ID_GRUPO), "Grupos", Not Nz(Me.TxtExpandirGrupo)
Echo False
Me.Requery
Echo True
End Sub

 

Estos eventos llaman a un procedimiento en un módulo general que busca en la tabla tblExpandir el ID y el texto identificativo de la tabla (“Títulos” o “Grupos”) que pasamos como argumento y cambia el valor de el campo Expandir por el que también le pasamos. Si no encuentra el registro, llama a otro procedimiento que lo añade con los valores de los argumentos que hemos pasado al primero. Ninguna cosa del otro mundo, pero, para que quede mas claro, pego el código.

'---------------------------------------------------------------------------
' Procedure : fFindEditTblexpandir
' DateTime  : 11/02/13 20:33
' Author    : Chea
' Purpose   : Abrir un recordset de DAO , buscar por un campo y asignar valor
'---------------------------------------------------------------------------

Public Function fFindEditTblexpandir(BuscarlidPadre As Long, sNombreTablaPadre As String, bExpandir As Boolean) As Long

Dim miBD As DAO.Database
Dim rstblExpandir As DAO.Recordset

    On Error GoTo fFindEditTblexpandir_Error

Set miBD = CurrentDb()
Set rstblExpandir = miBD.OpenRecordset("tblExpandir", dbOpenDynaset)
With rstblExpandir
    If Not (.EOF And .BOF) Then
        .FindFirst "(idPadre = " & BuscarlidPadre & ") AND ( NombreTablaPadre = '" & sNombreTablaPadre & "')"
        If Not .NoMatch Then
            .Edit
                !Expandir = bExpandir
            .Update
            .Bookmark = .LastModified
            fFindEditTblexpandir = !Id
        Else
            fAddTblexpandir BuscarlidPadre, sNombreTablaPadre, bExpandir
        End If
    Else
        fAddTblexpandir BuscarlidPadre, sNombreTablaPadre, bExpandir
    End If
    .Close
End With
Set rstblExpandir = Nothing
Set miBD = Nothing

fFindEditTblexpandir_Salida:

   On Error GoTo 0
   Exit Function

fFindEditTblexpandir_Error:

   MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure fFindEditTblexpandir "
   GoTo fFindEditTblexpandir_Salida:

End Function

'---------------------------------------------------------------------------
' Procedure : fAddTblexpandir
' DateTime  : 11/02/13 21:11
' Author    : Chea
' Purpose   : Abrir un recordset de DAO , añadir un nuevo registro y asignar valor
'           : a los campos de ese registro con los valores que se pasen como parámetros.
'---------------------------------------------------------------------------

Public Function fAddTblexpandir(lidPadre As Long, sNombreTablaPadre As String, bExpandir As Boolean) As Long

Dim miBD As DAO.Database
Dim rstblExpandir As DAO.Recordset

    On Error GoTo fAddTblexpandir_Error

Set miBD = CurrentDb()
Set rstblExpandir = miBD.OpenRecordset("tblExpandir", dbOpenDynaset)
With rstblExpandir
        .AddNew
                !idPadre = lidPadre
                !NombreTablaPadre = sNombreTablaPadre
                !Expandir = bExpandir
        .Update
        .Bookmark = .LastModified
    fAddTblexpandir = !Id
    .Close
End With
Set rstblExpandir = Nothing
Set miBD = Nothing

fAddTblexpandir_Salida:

   On Error GoTo 0
   Exit Function

fAddTblexpandir_Error:

   MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure fAddTblexpandir "
   GoTo fAddTblexpandir_Salida:

End Function

Hay algo mas de código, por ejemplo, para llamar al evento ExpandirTitulo_Click, cuando pulsamos sobre el botón que cubre el campo, o para detectar pulsaciones de teclas y pasarlas al evento Click correspondiente.  No lo ponemos para que quede mas claro el funcionamiento básico.

 

Conclusión

Los informes, usados generalmente como subinformes, son una alternativa al Control Treeview, sencilla, completamente funcional y con mas posibilidades. Estamos utilizando elementos nativos de Access y algo tan sumamente familiar como son los informes. No vamos a tener que instalar o registrar nada ajeno a Acces, ni vamos a tener nunca problemas por cambio de versiones o características obsoletas. Se necesita menos código que con un TreeView Control y el diseño gráfico de los informe facilita muchísimo la tarea.

Se pueden añadir todos los campos que se quiera, del tipo que se quiera y en la posición que se quiera. No tenemos por qué limitarnos al clásico TreeView.

Seguramente es mas lento que un TreeView y produce un ligero parpadeo cada vez que hacemos Requery (contraemos o expandimos) Este parpadeo es mas notorio cuanto mas cosas le pidamos al informe, por ejemplo, formatos condicionales, totales y en general todo lo que signifique cálculos en el informe, pues los campos afectados tardan una pizca mas el mostrarse. Pero algún precio habría que pagar.

 
Publicado por Chea con no comments

Apuntes para una seguridad a nivel de usuario en Access 2010 II. Macros de datos

Después de casi dos años tomando impulso, por fin me animo a continuar con la seguridad a nivel de usuario usando las macros de datos. El que consiga acabar este artículo me disculpará por el retraso, pues se dará cuenta de que hacía falta tener ánimo para ponerse a hablar de las macros de datos: Apenas nadie utiliza macros, el editor y muchas características son completamente nuevos y las macros de datos son una novedad absoluta en Access 2010, a lo que se añade que apenas existen ayuda o facilidades al respecto.

Tendría que explicar el funcionamiento de las macros, los tipos, sus posibilidades, las macros de datos y, cuando empezara por la forma de usarlas para asegurar una tabla a nivel de usuario, ya habría escrito todo un tratado, seguramente muy aburrido. Así que voy directamente al grano, a empezar dando unas ideas de cómo crear macros que eviten que los usuarios no autorizados expresamente manipulen los datos de las tablas y, a medida que las voy explicando, mencionaré pormenores y detalles de las macros y de su editor. Espero que se entienda algo.

En el artículo anterior veíamos que, para asegurar una base de datos Access, es necesario hacer inaccesible el back-end y dábamos unas pistas acerca de cómo protegerlo. Si un usuario sin permiso expreso puede acceder directamente al contenido de las tablas, o, peor aún, a su diseño, todo lo hablado aquí no sirve para nada. Una vez conseguido esto, podemos abordar la utilización de macros de datos para que solo los usuarios autorizados puedan añadir, editar o borrar datos solo en las tablas para las que se les haya concedido permiso. Tenga en cuenta que solo son unos apuntes y que es su propia responsabilidad procurar que no queden agujeros en la seguridad.

Grosso modo, lo que vamos a hacer con las macros es verificar si el usuario tiene permisos para la operación que está pretendiendo hacer y, si no los tiene, abortarla. Un paso mas adelante, aprovechando de que conocemos el  usuario, vamos a guardar en cada registro qué usuario lo crea,  lo que aparte de un rudimentario seguimiento de cambios, nos permite dar permisos para que sólo el autor de un registro pueda modificarlo.

Las macros de datos

Las macros de datos son nuevas en Access 2.010 y el editor de macros, de datos o no, también es nuevo, por lo que no es de extrañar que haya muy poco publicado al respecto.

Las macros de datos están disponibles en la Vista Diseño de la tabla, en la pestaña Diseño de la cinta de opciones, en el grupo de “Eventos de campo, registro y tabla”

 

image

 

Es decir, en Access 2010 las tablas tienen eventos y podemos escribir macros en respuesta a estos eventos. Los evento son Después de insertar, Después de actualizar, Después de eliminar, Validar eliminación y Antes del Cambio. Desde el punto de vista de la seguridad por usuarios, los tres primeros son apropiados para registrar al usuario que produce el evento, y los dos últimos para verificar si el usuario tiene los permisos necesarios y, en caso contrario, abortar la operación.

Vamos a empezar por algo muy, muy básico, impedir que se pueda borrar un registro accidentalmente, pero antes debemos abrir el editor de macros.

 

El editor de macros

Da la sensación de que el editor de macros en Access 2010 está a medio acabar, pues le faltan cosas tan elementales como un menú contextual para copiar y pegar, que, sin embargo, ya existe en 2013, y la ayuda es mínima. No obstante, lo esencial funciona correctamente y sirve a la perfección para nuestro propósito.

Pinchando en uno de los eventos del desplegable de la cinta de opciones que veíamos antes, se abre el editor de macros. Las acciones disponibles varían según el tipo de macro y el tipo de eventos. Es decir, las macros de datos tienen muchas menos acciones disponibles y los eventos Validar eliminación y Antes del cambio, menos aún. Y, sin embargo, con estas macros se pueden conseguir cosas antes impensables.

image

 

Hemos abierto el editor de macros para el evento Validar eliminación. A la derecha se muestran todas las acciones disponibles, bien pocas, y a la izquierda una ventana de edición, más grande con un símbolo mas image y un cuadro desplegable en el que podemos elegir la acción que queremos anotar. Si no se mostrara el catálogo de acciones, podemos hacerlo visible pulsado en el botón “Catálogo de acciones” de la cinta de opciones.

Las acciones se eligen en el cuadro desplegable o se arrastran desde el Catálogo de acciones que tenemos a la derecha. No es un editor donde podamos escribir código, solo elegir entre las opciones que tenemos y tan solo escribiremos algunas expresiones cuando sean parámetros de la acción que hemos elegido.

Nuestra primera macro

Vamos a crear nuestra primera macro. En el desplegable elegimos Provocar error.

image

 

Escribimos el valor de los parámetros que nos pide, el Número de error, que es el que queramos, y la Descripción del error, que será el mensaje que se muestre al producirse el error. Guardamos y ya tenemos nuestra primera macro de datos

image

 

Abra la tabla e intente borrar un registro. Imposible, nos sale el mensaje que hemos puesto en la descripción del error: ¡Prohibido borrar! y ni en un formulario, ni por código, ni por consulta de eliminación, ni directamente en la Vista Hoja de datos de la tabla podremos borrar ningún registro.

 

image

 

Bueno, nos hemos pasado, hemos de dar alguna posibilidad de borrar un registro. Por ejemplo, podemos añadir a la tabla un campo Sí/No que se llame Borrar, y, si es verdadero, sí se puede borrar el registro.

Ahora, lo que tenemos que hacer con la macro es comprobar el valor del Campo Borrar y, si no es verdadero, provocar el error.

Vamos a la Vista Diseño de la tabla y abrimos la macro del evento Validar eliminación. Arrastramos el grupo Si al editor e introducimos las expresiones, para lo cual contamos con la ayuda del estupendo generador de expresiones

 

image

 

image

 

En la condición escribimos [Borrar] <> True  image… Entonces, lo que tenemos que hacer es provocar el error, como hemos hecho antes. Pero el código para provocar el error se encuentra después de Finalizar si, es decir fuera del bloque If..,Endif. Vamos a arreglarlo.

 

Observe que el bloque donde estamos introduciendo las expresiones está marcado, delimitado por un cuadrado y con fondo mas oscuro. Si pinchamos con el ratón sobre el bloque de ProvocarError, es éste el que queda marcado y, si movemos el ratón manteniéndolo pulsado, el bloque se mueve.

image

 

Pues bien, arrastramos el bloque de ProvocarError dentro del bloque Si…Finalizar si y voilà, ya tenemos nuestra macro:

image

 

Guardamos la macro, cerramos y comprobamos si podemos borrar. Podremos hacerlo si, previamente, para ese registro hemos marcado el campo Borrar como verdadero. Es sencillo y, tal como está, vale para cualquier tabla, por lo que vamos a copiarla para pegar en otras tablas y mandarnos el código por correo al trabajo, para hacerlo también allí.

Editamos de nuevo la macro.

image

 

Como no tenemos marcado ningún bloque, el código se queda mas limpio, mas claro. Podemos observar que nos ha cambiado True por Verdadero, que a las macros les gusta el castellano, incluso a veces no entienden el inglés.

Arrastramos el ratón para seleccionar el texto, pero no se selecciona nada, en cambio, si pinchamos, se seleccionan los bloques que hemos visto antes. Si tenemos cuidado y/o pulsamos también
Mayúsculas o Control al tiempo que el ratón, podemos seleccionar todos los bloques en un bloque único.

Buscamos un botón para copiar, pero no existe, pulsamos el botón derecho del ratón y no aparece ningún menú (bueno, si tienes instalado también Access 2013, sí aparece) ¿Cómo copiamos? Pues como hacíamos antes de los ratones, con Control+C.

Probamos, abrimos otra macro y pegamos. Se pega correctamente y se replica el texto la macro que habíamos copiado.

Llegados a este punto de lectura, usted ya tiene ganas de probar el editor de macros, pero quiere darse prisa y empezar copiando el código desde este blog ¿Desde dónde, si todo son imágenes? Está bien, ahí va el código de la macro:

<?xml version="1.0" encoding="UTF-16" standalone="no"?> 
<DataMacros
xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro
Event="BeforeDelete"><Statements><ConditionalBlock><If><Condition>[Borrar]&lt;&gt;True</Condition><Statements><Action
Name="RaiseError"><Argument
Name="Number">1000</Argument><Argument
Name="Description">¡Prohibido
borrar!</Argument></Action></Statements></If></ConditionalBlock></Statements></DataMacro></DataMacros>

Efectivamente, las macros se guardan como código xml y, si usted copia ese texto y lo pega en el editor de macros, el resultado se mostrará como la macro original. Para pegar, no pinche sobre la ventana de edición de la macro, sino sobre la pestaña de la ventana.

 

Verificar permisos de un usuario

Ya tenemos nuestra primera macro de datos y consigue algo que resulta imposible en cualquier versión anterior de Access, pero de lo que trata este artículo es de la seguridad a nivel de usuarios y lo que queremos es poder discriminar qué usuarios pueden borrar y cuáles no. Para ello debemos empezar por una tabla con los permisos de los usuarios, buscar si el usuario actual tiene permiso de borrado y, en caso contrario, provocar un error.

La cuestión ahora es cómo pasamos a la macro el nombre del usuario para que lo busque en la tabla permisos. Las macros de datos no pueden usar ninguna función personalizada, ni siquiera Variables Temporales (Variables Locales, que no son globales, sí) ni existe ninguna propiedad o función incorporada que nos devuelva el usuario de Access o el de Windows y que sea accesible para la macro de datos.

El truco está en que, aunque no se muestren en el generador de expresiones, se pueden hacer búsquedas en consultas y las consultas pueden utilizar una función personalizada como origen de un campo calculado y que las macros de datos pueden buscar en una consulta igual que en una tabla, aunque, para despistar, no se muestren en el generador de expresiones ni el Intellisense para las macros. O sea, que podemos tener una consulta que tenga una función que devuelva el nombre del usuario y la macro de datos lo lea como si fuera un campo.

Las macros de datos y las consultas se ejecutan en el Front-End, aunque se alojen en el Back-End, por lo que la función que devuelva al usuario debe encontrarse en el Front-End y, si no queremos ir haciendo pruebas mientras diseñamos la macro, conviene que el Back-End tenga una copia de esta función. Cómo se gestiona el login de cada usuario y cómo se obtiene la función que devuelve el nombre del usuario actual es cosa personal de cada cual y no se va a tratar aquí, aunque adelanto que para eso también se podrían utilizar macros de datos.

En resumen, la macro debe buscar en una consulta los permisos del usuario actual, obtenido en la consulta mediante una función, y si carece de permiso para borrar en esta tabla, producir un error.

De momento, nos vale con una cosa muy sencillita, basta con que, para cada tabla que queremos proteger, recoja los permisos de cada usuario autorizado.

 

image

 

Se supone que cada cual tendrá construido un sólido sistema para el control de los usuarios que pueden entrar en nuestra aplicación, con usuarios, contraseñas, registro de logins, etc., y la forma de recuperar el usuario actual. Sin embargo, para las pruebas no vale cualquier cosa que nos devuelva un usuario, por ejemplo, el usuario de Windows; así que nos hacemos esta función.

Public Function fUsuarioActual()
fUsuarioActual = Environ("USERNAME")
End Function

Y con la tabla de permisos y la función que devuelve el usuario actual, nos podemos montar la consulta que nos devuelva los permisos del usuario actual:

SELECT tblPermisos.Usuario, tblPermisos.Tabla, tblPermisos.Insertar, tblPermisos.Editar, tblPermisos.Borrar
FROM tblPermisos
WHERE (((tblPermisos.Usuario)=fUsuarioActual()));

Y se ve así:

image

 

Macro Validar Eliminación

 

Ahora en nuestra macro Validar eliminación podemos consultar fácilmente si el usuario actual tiene permiso para borrar y, en caso contrario, se lo impedimos. Esto en una macro, se refleja así:

image

 

Estudiemos un poco la macro, pues hemos metido alguna cosa nueva y, aunque parezca muy sencilla, tiene su intríngulis.

Está claro que hacemos una búsqueda en la consulta qPermisos con la condición de que el campo Tabla sea “Contactos”.

En vez de comprobar directamente el campo [Borrar], lo hemos pasado a una variable local de nombre PermisoBorrar, que es la que comprobamos después ¿Porqué?

Fíjese en tenemos en bloques distintos Buscar un registro en y Si Nz(PermisoBorrar…. Ocurre que las macros son muy puñeteras y hay que tener mucho cuidado con dónde va y qué va en cada bloque y, dependiendo de esto, la lógica cambia por completo. Fuera del bloque Buscar un registro en qPermisos, el campo [Permisos].[Borrar] no existe y por eso lo pasamos a una VariableLocal, PermisosBorrar, que tiene alcance en cualquier lugar de la macro. Observe que luego la forma de llamar a la variable es como a un campo, con el nombre entre corchetes.

¿Podríamos haber metido el bloque de Si Nz(PermisoBorrar…. dentro del de Buscar un registro en y así nos ahorrábamos la VariableLocal? Efectivamente, pero ya hemos comentado que al meter un bloque en otro cambia la lógica.

En un bloque Buscar un registro en las acciones que vayan dentro de él solo se ejecutan si se ha encontrado el registro buscado, de manera que, si no encuentra los permisos del UsuariActual, no se ejecutaría el código que provoca el error y el registro se borraría sin ningún problema.

Puede tener su lógica que los usuarios para los que no se han establecido permisos para una tabla puedan borrar en ésta, pero resulta mucho mas seguro al revés, que solo los usuarios a los que expresamente se ha dado permiso puedan borrar en la tabla. Tal como está la macro, si no existe el usuario para la tabla Contactos, no se ejecuta lo que hay dentro del bloque Buscar un registro en y, por tanto, no se establece la VariableLocal, de manera que en el siguiente bloque no la encuentra y Access provoca un error y muestra un msgbox con “No se encontró el identificador ‘[PermisoBorrar]’.”, de forma que no se puede borrar un registro. Es decir, si no existe el usuario, se produce un error de Access en la macro y no se puede borrar.

Ya tenemos completa la macro Validar eliminación y abajo pegamos el código xml. Si quiere trasladar esta macro a cualquier tabla, cópielo al portapapeles, abra la tabla en Vista Diseño, edite la macro, posicione el ratón sobre la pestaña y pulse Control+V. Solo le falta cambiar en la Condición Where el nombre de la tabla.

 

<?xml version="1.0" encoding="UTF-16" 
standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro

Event="BeforeDelete"><Statements><ConditionalBlock><If><Condition>[Borrar]&lt;&gt;True</Condition><Statements><Action

Name="RaiseError"><Argument
Name="Number">1000</Argument><Argument Name="Description">No se
puede borrar sin haber marcado
previamente el campo

[Borrar]</Argument></Action></Statements></If></ConditionalBlock><LookUpRecord><Data

Alias="Permisos"><Reference>qPermisos</Reference><WhereCondition>[Tabla]="Contactos"</WhereCondition></Data><Statements><Action

Name="SetLocalVar"><Argument
Name="Name">PermisoBorrar</Argument><Argument

Name="Value">Nz([Permisos].[Borrar])</Argument></Action></Statements></LookUpRecord><ConditionalBlock><If><Condition>Nz([PermisoBorrar])&lt;&gt;True</Condition><Statements><Action

Name="RaiseError"><Argument
Name="Number">2000</Argument><Argument Name="Description">Usted
no tiene permisos para

borrar</Argument></Action></Statements></If></ConditionalBlock></Statements></DataMacro></DataMacros>
 

Macro Antes del cambio

 

Todo lo que hemos contado para la macro Validar eliminación es válido para la macro Antes del cambio, por tanto, copiar y pegar y corregir algunas cosillas nos puede valer para empezar, aunque en la macro Antes del cambio hay que tener en cuanta alguna cosa mas.

Copiamos al portapapeles la macro Validar eliminación, abrimos la macro Antes de cambio, nos posicionamos en la pestaña y pegamos pulsando Control+V. Borramos el bloque de If Borrar <> Verdadero... Dentro del bloque Buscar un registro en, cambiamos la variable local PermisoBorrar por PermisoEditar y la expresión Nz([Permisos].[Borrar]) por Nz([Permisos].[Editar]) y, después, en el bloque If, [PermisoBorrar] por [PermisEditar] y en el mensaje de error borrar por editar, y el código de error por un número distinto.

La macro queda así:

image

 

Con esto debería valernos. Probamos y funciona. Sin embargo, falta algo…

¿Macro Antes de insertar?

No. No existe la macro de datos Antes de insertar, pero tenemos que saber de alguna manera si estamos intentando insertar un nuevo registro. Para ello contamos con la propiedad EsInsertar

Propiedad EsInsertar

En la macro Antes del cambio (y también en la macro Después de Actualizar) podemos consultar la propiedad EsInsertar. (La escasa información que hay sobre macros de datos de Access en internet está inglés, y en inglés es IsInsert, sin embargo, si tratamos de utilizar IsInsert en nuestro Access en Castellano, no lo reconocerá)

Entonces, en nuestra macro Antes del cambio debemos comprobar también si se trata de insertar y, en ese caso, comprobar en la tabla permisos si hay permiso para insertar y actuamos en consecuencia.

No es necesaria una nueva búsqueda. En la misma búsqueda añadimos una variable local mas, PermisoInsertar, y le asignamos el valor del campo insertar.. En un bloque siguiente, comprobamos la propiedad EsInsertar y, si es verdadera, verificamos el valor de PermisoInsertar y, si no, el de PermisoEditar y, si no lo hay, provocamos un error.

 

image

 

Lo probamos y funciona, así que ya podemos pegar el código xml por si alguien mas lo quiere probar.

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro Event="BeforeChange"><Statements><LookUpRecord><Data Alias="Permisos"><Reference>qPermisos</Reference><WhereCondition>[Tabla]="Contactos"</WhereCondition></Data><Statements><Action Name="SetLocalVar"><Argument Name="Name">PermisoEditar</Argument><Argument Name="Value">Nz([Permisos].[Editar])</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">PermisoInsertar</Argument><Argument Name="Value">[permisos].[insertar]</Argument></Action></Statements></LookUpRecord><ConditionalBlock><If><Condition>[IsInsert]</Condition><Statements><ConditionalBlock><If><Condition>Nz([PermisoInsertar])&lt;&gt;True</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">4000</Argument><Argument Name="Description">Usted no tiene permisos para Insertar</Argument></Action></Statements></If></ConditionalBlock></Statements></If><Else><Statements><ConditionalBlock><If><Condition>Nz([PermisoEditar])&lt;&gt;True</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">3000</Argument><Argument Name="Description">Usted no tiene permisos para Editar</Argument></Action></Statements></If></ConditionalBlock></Statements></Else></ConditionalBlock></Statements></DataMacro></DataMacros>

Lo mismo que con la macro Validar eliminación, podemos pegarlo en cualquier tabla y solo tendríamos que cambiar en la Condición Where el nombre de la tabla.

Incluyendo estas dos macros en cualquier tabla, conseguimos que ningún usuario que no esté expresamente autorizado pueda insertar, editar o borrar ningún registro. Obviamente, antes hay que asegurarse de que no tenga acceso directamente al back-end, pues podría modificar las macros o la tabla permisos, a la que también deberíamos dar permisos, y de que contamos con un sistema de identificación de usuario y verificación del inicio de sesión suficientemente seguros, pero eso corresponde al lector y va mas allá de lo que se puede tratar en este artículo.

Identificar al usuario que crea cada registro

Tal como hemos dejado las macros, son suficientes para asegurar una tabla, pero hemos afirmado que íbamos a dar un paso mas y lo vamos a hacer. Primero, vamos a identificar en cada registro qué usuario lo ha creado y. luego, vamos a añadir un tercer tipo de permiso, aparte de sí o no, que solo el usuario que ha creado el registro pueda editarlo o borrarlo.

Lo de identificar al usuario es sencillo. Tenemos que añadirle un campo mas a nuestra tabla, que llamaremos CreadoPor. En la macro añadimos una VariableLocal mas, [Usuario], y, si EsInsertar asignamos a CreadoPor el valor de [Usuario] utilizando la acción de macro EstablecerCampo. Así:

image

 

Pero surge una pega: cualquiera podría modificar el valor del campo CreadoPor y, por tanto editar o borrar el registro. Tenemos que evitarlo; si no EsInsertar y alguien intenta modificar el campo CreadoPor debemos provocar un error, pero ¿Cómo saber si se intenta modificar un campo concreto?

Función Actualizado

En la macro Antes del cambio la función Actualizado (“Nombre de campo”) nos dice si el campo cuyo nombre pasamos en una cadena como argumento se ha cambiado. Entonces, en nuestra macro podemos comprobar, si no EsInsertar, si Actualizado (“CreadoPor”) es verdadero y, en ese caso provocamos un error para evitarlo. La parte final de la macro (no cabe entera en la pantalla para copiarla) queda así:

 

image

 

Objeto Antiguo

Provocando un error hemos evitado que se cambie el valor de CreadoPor, pero resulta muy poco amable bloquear la edición de todo un registro porque el usuario ha intentado modificar un campo que no debía ¡A lo mejor ha sido sin querer! Lo que podemos hacer es, en vez de provocar el error, volver el valor del campo a su estado original, y para eso contamos con el objeto Antiguo. Basta con escribir Antiguo. en una expresión y el Intellisense nos muestra los campos a los que se puede aplicar

 image

Si queremos se mas amables, podemos sustituir la acción de ProvocarError por una de EstablecerCampo en la que el valor original se sustituye por el Antiguo. El final de la macro modificada queda de la siguiente manera.

image

 

Y el código xml:

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro Event="BeforeChange"><Statements><LookUpRecord><Data Alias="Permisos"><Reference>qPermisos</Reference><WhereCondition>[Tabla]="Contactos"</WhereCondition></Data><Statements><Action Name="SetLocalVar"><Argument Name="Name">PermisoEditar</Argument><Argument Name="Value">Nz([Permisos].[Editar])</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">PermisoInsertar</Argument><Argument Name="Value">[permisos].[insertar]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">UsuarioActual</Argument><Argument Name="Value">[permisos].[Usuario]</Argument></Action></Statements></LookUpRecord><ConditionalBlock><If><Condition>[IsInsert]</Condition><Statements><ConditionalBlock><If><Condition>Nz([PermisoInsertar])&lt;&gt;True</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">4000</Argument><Argument Name="Description">Usted no tiene permisos para Insertar</Argument></Action></Statements></If></ConditionalBlock><Action Name="SetField"><Argument Name="Field">CreadoPor</Argument><Argument Name="Value">[UsuarioActual]</Argument></Action></Statements></If><Else><Statements><ConditionalBlock><If><Condition>Nz([PermisoEditar])&lt;&gt;True</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">3000</Argument><Argument Name="Description">Usted no tiene permisos para Editar</Argument></Action></Statements></If></ConditionalBlock><ConditionalBlock><If><Condition>Updated("Contactos.CreadoPor")</Condition><Statements><Action Name="SetField"><Argument Name="Field">CreadoPor</Argument><Argument Name="Value">[Old].[CreadoPor]</Argument></Action></Statements></If></ConditionalBlock></Statements></Else></ConditionalBlock></Statements></DataMacro></DataMacros>

Permitir cambios solo al usuario que ha creado el registro

No tiene mucho sentido identificar al usuario que ha creado el registro y que luego lo pueda editar cualquiera. Una alternativa es identificar también al usuario que modifica el registro, para lo necesitaríamos un nuevo campo, ModificadoPor, al que le daríamos valor de la misma manera que a CreadoPor, solo que cuando no sea EsInsertar (estaría bien añadir también campos de fecha para la creación y la modificación). Otra alternativa, que no es incompatible con la anterior, es crear un sistema de permisos en el que, en los que Borrar y Editar tengan valores equivalentes “Sí”, “No” y “Sólo los registros creados por mí”, que es lo que vamos a hacer ahora.

Empezamos por modificar la tabla de permisos, cambiando Editar y Borrar de Sí/No a numérico con los valores 0,1,2 (No,Sí y ‘Los propios’)

 

image

 

Ahora, en la macro tenemos que comprobar, si no es EsInsertar, si tiene permisos para editar todo o solo para editar los registros propios y, en ese caso, verificar el creador del registro.

 

La parte de la macro que comprueba los permisos para modificar queda así:

 

image

 

Y el código xml de la macro definitiva Antes del cmbio es el siguiente:

 

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro Event="BeforeChange"><Statements><LookUpRecord><Data Alias="Permisos"><Reference>qPermisos</Reference><WhereCondition>[Tabla]="Contactos"</WhereCondition></Data><Statements><Action Name="SetLocalVar"><Argument Name="Name">PermisoEditar</Argument><Argument Name="Value">Nz([Permisos].[Editar])</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">PermisoInsertar</Argument><Argument Name="Value">[permisos].[insertar]</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">UsuarioActual</Argument><Argument Name="Value">[permisos].[Usuario]</Argument></Action></Statements></LookUpRecord><ConditionalBlock><If><Condition>[IsInsert]</Condition><Statements><ConditionalBlock><If><Condition>Nz([PermisoInsertar])&lt;&gt;True</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">4000</Argument><Argument Name="Description">Usted no tiene permisos para Insertar</Argument></Action></Statements></If></ConditionalBlock><Action Name="SetField"><Argument Name="Field">CreadoPor</Argument><Argument Name="Value">[UsuarioActual]</Argument></Action></Statements></If><Else><Statements><ConditionalBlock><If><Condition>Nz([PermisoEditar])&lt;&gt;True</Condition><Statements><ConditionalBlock><If><Condition>[PermisoEditar]=0</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">3000</Argument><Argument Name="Description">Usted no tiene permisos para Editar</Argument></Action></Statements></If></ConditionalBlock><ConditionalBlock><If><Condition>[PermisoEditar]=2</Condition><Statements><ConditionalBlock><If><Condition>[Contactos].[CreadoPor]&lt;&gt;[usuarioActual]</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">6000</Argument><Argument Name="Description">Sólo puede modificar registros en la tabla 'Contactos' si es el creador del registro</Argument></Action></Statements></If></ConditionalBlock></Statements></If></ConditionalBlock></Statements></If></ConditionalBlock><ConditionalBlock><If><Condition>Updated("Contactos.CreadoPor")</Condition><Statements><Action Name="SetField"><Argument Name="Field">CreadoPor</Argument><Argument Name="Value">[Old].[CreadoPor]</Argument></Action></Statements></If></ConditionalBlock></Statements></Else></ConditionalBlock></Statements></DataMacro></DataMacros>

 

Ya solo nos queda hacer los cambios en la macro Validar eliminación para que pueda permitir borrar los registros propios. Como es similar a lo que hemos visto, me limito a pegar el código xml.

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application"><DataMacro Event="BeforeDelete"><Statements><ConditionalBlock><If><Condition>[Borrar]&lt;&gt;True</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">1000</Argument><Argument Name="Description">No se puede borrar sin haber marcado previamente el campo [Borrar]</Argument></Action></Statements></If></ConditionalBlock><LookUpRecord><Data Alias="Permisos"><Reference>qPermisos</Reference><WhereCondition>[Tabla]="Contactos"</WhereCondition></Data><Statements><Action Name="SetLocalVar"><Argument Name="Name">PermisoBorrar</Argument><Argument Name="Value">Nz([Permisos].[Borrar])</Argument></Action><Action Name="SetLocalVar"><Argument Name="Name">UsuarioActual</Argument><Argument Name="Value">Nz([Permisos].[Usuario])</Argument></Action></Statements></LookUpRecord><ConditionalBlock><If><Condition>Nz([PermisoBorrar])=False</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">2000</Argument><Argument Name="Description">Usted no tiene permisos para borrar</Argument></Action></Statements></If></ConditionalBlock><ConditionalBlock><If><Condition>[PermisoBorrar]=2</Condition><Statements><ConditionalBlock><If><Condition>[Contactos].[CreadoPor]&lt;&gt;[UsuarioActual]</Condition><Statements><Action Name="RaiseError"><Argument Name="Number">5000</Argument><Argument Name="Description">Sólo puede borrar aquellos registros creados por  vd.</Argument></Action></Statements></If></ConditionalBlock></Statements></If></ConditionalBlock></Statements></DataMacro></DataMacros>

Conclusión

Las macros en Access 2010 no serían difíciles si las utilizáramos con frecuencia, pero eso nunca va a ser ocurrir pues, para casi todo, es mas sencillo y mas cómodo utilizar VBA con el que, además, podemos llegar mucho mas lejos. Sin embargo, las macros de datos nos permiten hacer cosas impresionantes, impensables en versiones anteriores de Access e imposibles de hacer con código VBA, por lo tanto, merece la pena pagar el precio de trabajar, aunque sea torpemente, con estas macros pesadas y puñeteras e ir aprendiendo algo.

A las macros de datos les queda recorrido aparte de lo tratado aquí. No hemos vistos nada de las otras macros de datos, las de “después de”, ni de las macros de datos con nombre. Desde el punto de vista de la seguridad a nivel de usuario, nos permitirían, por ejemplo, hacer un seguimiento de cambios, con registro del usuario que los realiza y del valor anterior. Algún día, espero no tardar tanto como en esta ocasión, escribiré sobre ello.

Publicado por Chea con 1 comment(s)

Apuntes para una seguridad a nivel de usuario en Access 2010. Proteger el back-end

Las versiones de Access anteriores a la 2007 incluían un sistema de seguridad a nivel de usuario basado en grupos de trabajo bastante completo… salvo por el hecho de que, al menos en los últimos años, no servía absolutamente para nada. Resultaba del todo inútil identificar usuario y contraseña y asignar permisos en consecuencia si, con una sencilla búsqueda en interné, encontrábamos varias aplicaciones gratuitas con las que piratear las contraseñas. En consecuencia, en Access 2007 se abandona esta seguridad tan insegura.

A cambio, no se ha implementado ningún sistema de seguridad a nivel de usuario, aunque se afirma que la seguridad ha mejorado notablemente, pues ahora las bases de datos se pueden proteger por contraseña al tiempo que se cifran por un algoritmo muy seguro. A partir de ese cifrado con contraseña, cada uno debe implementar su propio sistema de seguridad por usuarios. También podríamos guardar los datos en SQL-Server o en Sharepoint, utilizar la seguridad de éstos y olvidarnos de crear la nuestra propia, pero esto a menudo no es asequible.

Con archivos accdb, que es de lo que vamos a tratar, una posibilidad interesante es crear un sistema basado en los eventos de datos de Access 2010, de esa manera la lógica de la seguridad se traslada al back-end y se centra en las tablas en vez de hacerlo en la aplicación que las maneja.

Pero, para que el sistema sea seguro, tenemos que conseguir que el back-end sea completamente inaccesible para quien no cuente con los permisos necesarios y que ello no le impida ser accesible desde el fornt-end. La solución para esto ya está estudiada y vale lo mismo para Access 2007 que para Access 2010; será de lo que tratemos en este artículo y dejaremos para otro posterior cómo usar las macros de eventos de datos de Access 2010 para asegurar nuestros datos y, en un tercero, veremos cón utilizar las macros de datos para hacer un archivo de seguimiento, un log, de los cambios en los datos del back-end.

Proteger el back-end en Access 2007/2010

A partir de Access 2007, si protegemos nuestros datos con una contraseña, quedan a buen recaudo, puesto que la contraseña es mucho más segura que en versiones anteriores y, además, los datos se cifran.

El problema está en cómo hacer que nuestra aplicación acceda a esos datos sin que la contraseña quede expuesta. Podemos evitar tener tablas vinculadas y abrir los recordsets de todos los objetos mediante código, pero eso resulta pesado y se pierde la sencillez, que es la principal ventaja de Access. Al vincular tablas de una BD protegida, Access pide la contraseña y la guarda en la propiedad Connect del objeto TableDef correspondiente a esa tabla, de manera que basta con leer la propiedad Connect de la tabla correspondiente para conocer la contraseña

Es lo que se plantea en este hilo en UtterAccess http://www.utteraccess.com/forum/Access-2007-security-t1242310.html&p=1243573#entry1243573 y ahí mismo proponen una solución que, aunque luego se complica, es bastante sencilla y es de la que partimos: Las tablas se vinculan sin contraseña, lo cual al intentar abrirlas produciría un error, pero al inicio de la aplicación creamos, mediante código en el que indicamos la contraseña, un recordset que se mantiene abierto toda la aplicación y, al quedar abierta la conexión con la BD protegida, es innecesario indicarle de nuevo la contraseña, por lo que podemos abrir las tablas vinculadas.

Ocurre algo parecido cuando utilizamos conexiones ODBC, que la contraseña se guarda y queda accesible, y la solución es la misma, no guardar la conexión con contraseña, abrir una conexión mediante código y mantenerla abierta durante toda la aplicación. En http://blogs.office.com/b/microsoft-access/archive/2011/04/08/power-tip-improve-the-security-of-database-connections.aspx, otra vez en guiri, nos cuentan cómo hacerlo.

El código puede hacerse inaccesible, por ejemplo, convirtiendo la aplicación en ACCDE y/o protegiendo a su vez, previamente, el proyecto VBA con otra contraseña. De esta manera, las tablas sólo se pueden abrir desde nuestra aplicación y, desde ella, controlamos quién y cómo puede acceder a los datos. Cualquiera podría importar nuestras tablas vinculadas desde otra BD, pero no sabiendo la contraseña, no le serviría de nada. También podría, si es un usuario autorizado, abrir directamente a las tablas de nuestra aplicación y saltarse así los permisos que damos mediante código, pero eso también podemos evitarlo, por ejemplo, comprobando que la aplicación se ejecuta en modo runtime justo antes de abrir el recordset que proporciana la clave al resto de la aplicación y, cuando sepamos asegurar los datos con macros de datos, ni siquiera podrán modicar nada sin permiso, aunque accedan a las tablas.

En resumen, los pasos para proteger nuestro back-end de una forma muy sencilla serían los siguientes:

1º- Vincular las tablas del back-end antes de cifrarlo con contraseña (de esa manera la contraseña no se guardará en la propiedad Connect)

2º - Cifrar con contraseña el back-end

image    image

3º -  Al inicio de nuestra aplicación abrir un formulario invisible (o un módulo de clase) en el que asignaremos la contraseña mediante código, y que mantendrá abierta la conexión con el back-end durante toda la aplicación.

Este último punto puede parecer difícil, pero es sencillo haciéndolo de la siguiente manera:

  • Vinculamos una tabla con datos irrelevantes. Nos pedirá contraseña y se guardará, pero más tarde la borraremos. Como ya existiría esa misma tabla vinculada, se guardará con el mismo nombre y un ordinal, por ejemplo TablaTonta1.

image

  • A partir de la tabla recién vinculada, creamos el formulario con sus campos.

image

  • Cambiamos la propiedad “Origen del registro” del formulario para dejarla en blanco.

image

  • En el evento Load del formulario abrimos un recordset que tome los datos de la BD protegida y lo asignamos como recordset del formulario.
Private Sub Form_Load()
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT TablaTonta.* FROM TablaTonta IN '' [MS Access;PWD=Anchoas de Santoña;DATABASE=C:\Users\Chea\Documents\Back-end protegido.accdb]", dbOpenDynaset)
Set Me.Recordset = rs
End Sub
  • Probamos que el formulario se abre correctamente y cambiamos la propiead Visible del formulario a No.

    Hay un truco para obtener fácilmente una cadena SQL correcta que abra un recordset de una tabla en una BD externa protegida por contraseña:

    •  
      •  
        • En el editor de VB abrimos la ventana de inmediato, decimos que nos imprima la propiedad Connect de la tabla que tenemos vinculada con contraseña y copiamos el texto de la propiedad connect al portapapeles

    image

    •  
      •  
        • Abrimos el asistente para consultas para crear una nueva consulta y, antes de añadir una nueva tabla, copiamos el texto de la propiedad Connect a la propiedad de la consulta Cadena de conexión de origen

    image

    •  
      •  
        • Añadimos ahora la tabla que queramos. Observe que lo que se muestra ahora son las tablas y consultas de la base protegida (Sí, he dicho consultas, se puede hacer un Select a una consulta de una BD externa)
        • Seleccionamos los campos que queramos y copiamos el texto SQL resultante.

    image

    Con ese texto SQL podemos crear el recordset que asignaremos como origen de nuestro formulario.

    •  
      • Borramos la tabla que acabamos de vincular con contraseña: Ya no la necesitas, la única mención a la contraseña que existe en nuestra aplicación está oculta en el código que asigna el recordset al formulario oculto.

    Ya tenemos la manera de abrir una conexión con el back-end protegido que, permaneciendo abierta durante toda la aplicación, permita abrir otras tablas de ese back-end sin necesidad de indicar de nuevo la contraseña. Es decir, tenemos la llave para abrir los datos protegidos, sólo queda verificar el usuario antes de usar esa llave, por ejemplo, en el mismo formulario oculto, antes de asignar el recordset, podemos comprobar el usuario y sus permisos y, si no son los adecuados, en vez de asignar el recordset del formulario cerramos la aplicación. La manera de controlar usuario y permisos depende de cada cuál y no tiene mayor complicación, cuanto más personal sea, más segura, pero una forma muy sencilla podría ser tener una tabla con usuarios contraseñas y permisos en el back-end, con la que abriremos, mediante código, un recordset de la misma manera que hacíamos con la tabla tonta, para verificar los permisos, procurando primero haber borrado cualquier vínculo a la tabla.

    Con estos sencillos pasos conseguimos que no se pueda acceder a nuestros datos directamente, sino solo a través de nuestra aplicación, de manera que podemos controlar quién y cómo lo hace. Es bastante seguro para lo fácil que resulta, pero en el propio hilo en UtterAccess que citábamos más arriba, advierten de posibles grietas y posibles soluciones.

    La pega es que estamos protegiendo, o accediendo, a todo el archivo de back- end a la vez, y, si queremos dar permisos ppersonalizados por usuarios y tablas, debemos hacerlo por código, que sólo controla la edición de datos cuanto se hace a través de un formulario. El problema más obvio es que desde una instalación completa de Access, un usuario autorizado para abrir la aplicación puede pulsaf F11 para hacer que se muestren las tablas y editar los datos, saltándose todo el sistema de autorizaciones que hayamos implementado mediante código en los formularios.

    Si estamos usando Access 2007, se me ocurre la solución de forzar a que la aplicación se ejecute en modo runtime, que no muestra las tabla; simplemente bastaría con verificar en el formulario de apertura si SysCmd(acSysCmdRuntime) es verdadero y, en caso contrario, cerrar la aplicación. Otra opción sería tener varios archivos de back-end agrupando las tablas por niveles de privilegios.

    En cambio, si usamos Access 2010, cabe otra posibilidad, utilizar macros de datos en el back-end que comprueben los permisos del usuario antes de insertar, modificar o borrar datos en la tabla y, si carece de ellos, provocar un error que impida la actualización. Toda la lógica de la seguridad por usuarios se traslada al back-end, se centraliza y se hace más sencilla y, por tanto, más segura. De ello trataremos en un próximos artículo.

    Lo que aquí se ha expuesto no es más que unos apuntes que puedan servir de punto de partida para que cada cual desarrolle su propio sistema de seguridad; no obstante, sin complicar demasiado las cosas, podemos tomar algunas medidas adicionales:

    • Verificar que UserControl sea verdadero para evitar automatización
    • Comprobar que CodeProject es el mismo que CurrentProject para evitar la carga como librería
    • Proteger con contraseña el proyecto VBA y convertirlo en ACCDE.
    • Sustituir el formulario por un módulo de clase en el que el recordset sea Friend.
    • Ocultar las tablas utilizando el atributo dbHiddenObject.

    En un próximo artículo veremos, cómo utilizando un back-end cifrado con contraseña, podemos valernos de las macros de eventos de datos de Access 2010 para impedir que un usuario no autorizado pueda insertar, modificar o borrar datos, incluso aunque tuviera acceso a las tablas vinculadas del front-ed.

    Gestión de turnos de trabajo con Access

    En determinadas empresas, o en determinados servicios, se trabajan muchas más horas a las semana de las que puede hacer un trabajador. Servicios médicos, policía, bomberos, fundiciones, funerarias, etc. dan servicio 24 horas al día 7 días a la semana y, como no hay ningún trabajador capaz de tanto, deben hacer distintos turnos que cubran todo el servicio. También es necesario hacerlos en empresas, como centros comerciales, que sin abrir 24 horas abren más horas, o durante más días, de lo que corresponde a una jornada laboral normal.

    Los turnos son distintos dependiendo del tipo de trabajo, necesidades de la empresa, etc., por ejemplo, puede resultar que, dado el tipo de trabajo, por la noche se necesito menos personal, que se cierren los domingos o que no se cierre nunca, que se tenga derecho a más o menos días de descanso, etc. Dependiendo de esas necesidades, cada empresa organiza sus turnos de una manera distinta pero los resultados tienen cosas en común: Se elabora una secuencia de los distintos turnos o tipos de jornada que debe hacer un trabajador y se organiza a los trabajadores en grupos, de manera que empezando cada uno la secuencia en una fecha distinta, se solapen unos con otros y cubran toda la jornada.

    Supongamos un caso muy sencillo: Cuatro trabajadores deben cubrir mañana tarde y noche y libran un día de cada cuatro. La secuencia sería mañana, tarde, noche, libre MTNL y cada trabajador la iniciaría con un día de diferencia.

    Trabajador 1: MTNLMTNLMTNLMTNLMTNL…

    Trabajador 2: TNLMTNLMTNLMTNLMTNLM…

    Trabajador 3: NLMTNLMTNLMTNLMTNLMT…

    Trabajador 4: LMTNLMTNLMTNLMTNLMTN…

    Cada columna representa un día y con este esquema se garantiza que todos los días haya un trabajador de tarde, otro de noche y otro librando.

    La mayor parte de los esquemas serán más complejos que éste, pero lo normal es que todos tengan una secuencia de turnos fija comenzando en distinto momento para cada grupo de trabajadores. Tomando esas secuencias y esos grupos como punto de partida, vamos a proponer una serie de soluciones que faciliten la gestión de grupos de trabajo utilizando una aplicación hecha en Access.

    Averiguar el turno para una fecha

    Necesitamos saber qué turno de trabajo le corresponde a un trabajador o grupo en determinada fecha. La primera tentación puede ser recorrer un bucle partiendo desde un turno conocido, sin embargo, hay otro planteamiento que resulta familiar, pues es el del calendario, es decir, de la misma manera que ordenamos las fechas, que son números consecutivos, en 7 columnas, una para cada día de la semana, y luego podemos mediante una función saber qué día de la semana es una determinada fecha,  podemos ordenarlas en tantas columnas como tenga la secuencia.

    Enero:

    M

    T

    N

    L

    1

    2

    3

    4

    4

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

     

    Para averiguar la columna que corresponde a un determinado item, hace años que descubrí el Mediterráneo al hacerme esta función:

     

    '---------------------------------------------------------------------------------------
    ' Procedure : fColumna
    ' DateTime  : 17/10/10 15:10
    ' Author    : Chea
    ' Purpose   : Dada una relación de números consecutivos ordenados en columnas, devuelve la
    '             columna que le corresponde a un determinado item
    '---------------------------------------------------------------------------------------
    '
    Public Function fColumna(item As Long, Columnas As Long) As Long
    fColumna = 1 + ((item + Columnas - 1) Mod (Columnas))
    End Function

     

    La función vale para resolver muchos problemas si se pueden plantear a partir de números consecutivos ordenados en columnas, pero es algo genérico que presupone que la primera columna es el número 1, lo cual, como en el ejemplo, no siempre es el caso y tenemos que considerar en qué columna se encuentra el número 1. Para eso con los turnos de trabajo he creado una función específica.

    '---------------------------------------------------------------------------------------
    ' Procedure : fTurnoFecha
    ' DateTime  : 17/10/10 15:12
    ' Author    : Chea
    ' Purpose   : Dada una determinada cadencia de turnos expresada en una cadena, sCadena, en la que
    '             los turnos vienen separados por comas, obtiene el turno que corresponde a una determinada
    '             fecha, FechaCalculo. Es necesario conocer una fecha de referencia, PrimerDiaTurno,
    '             que coincida con el primer día de la cadencia.
    '             Usando una fecha fija como PrimerDiaturno,podemos pasar un parámetro opcional,
    '             lDesplazamiento, para indicar cuantos días nos desplazamos del PrimerDiaturno.
    '             Por ejemplo, podemos tener distintos grupos, cada uno empezando en un día
    '             distinto, el primero tendrá desplazamiento 0, el segundo empezará un día más tarde, etc.
    '---------------------------------------------------------------------------------------
    '
    Public Function fTurnoFecha(FechaCalculo As Date, sCadencia As String, PrimerDiaTurno As Date, Optional lDesplazamiento As Long) As String
    Dim v As Variant
    Dim lColumnas As Long
    Dim lCompensacion As Long
    
    v = Split(sCadencia, ",")
    lColumnas = 1 + UBound(v)
    
    lCompensacion = fColumna(Int(PrimerDiaTurno + lDesplazamiento), lColumnas) - 1
    fTurnoFecha = v(fColumna(Int(FechaCalculo) - lCompensacion, lColumnas) - 1)
     
    End Function

     

    A la función le pasamos FechaCalculo, que es la fecha para la que queremos obtener el turno, sCadencia, que es una cadena en la que indicamos la secuencia del turno, con los items separados por comas, por ejemplo “M,T,N,L” o “Mañana,Tarde,Noche,Libre”. También le pasamos PrimerDiaTurno que es una fecha de la que sepamos con certeza que se corresponde con el primer día de la secuencia o, de manera opcional, lDesplazamiento, que podemos utilizar para indicar cuantos días se desplaza de una fecha fija, por ejemplo 0, el primer día del turno, calculándolo por tanteo.

    Por ejemplo, el día en que, tras una ardua negociación de empresa y trabajadores, se elaboraron las planillas, se comprobó que el trabajador 2 debía iniciar su secuencia el 4 de enero del 2007 y se anotó esta fecha para posteriores cálculos, según esto, para cálcular cómo le toca la Nochebuena del 2010, el cálculo en la ventana de inmediato sería:

     

    ? fTurnoFecha (#2010/12/24#,"Mañana,Tarde,Noche,Libre",#2007/01/04#)
    Noche

    Aparte de para hacerle la puñeta al trabajador 2, la función se puede utilizar en una consulta para obtener una relación de todos los turnos de todos los trabajadores para todos los días de un periodo determinado.

     

    Elaborar un calendario con turnos

    Tomemos una tabla de empleados, con indicación de a qué grupo pertenecen

    EmpleadosTurnos
    Id Empleado Grupo
    1 Pedro 1
    2 Andrés 1
    3 Jaime 1
    4 Juan 2
    5 Felipe 2
    6 Bartolomé 2
    7 Tomás 3
    8 Mateo 3
    9 Santiago 3
    10 Judas 4
    11 Simón 4
    13 Matías 4

    Aunque el último ID sea 13, se trata de 12 empleados, pues despidieron a uno por conducta desleal. Están organizados en 4 grupos cada uno de los cuales se inicia al día siguiente del grupo anterior. Vamos a crear el calendario correspondiente a octubre del 2010 utilizando una consulta.

    Sabemos que si disponemos de una tabla Numeros poblada de numeros consecutivos empezando por el 1, podemos obtener una secuencia de fechas a base de sumar una FechaInicial menos uno al campo Numero. Este truco y el de no relacionar tablas para que el resultado sea un producto cartesiano lo vamos a utilizar en la siguiente consulta.

     

     

    image

     

    El texto SQL es el siguiente:

     

    SELECT EmpleadosTurnos.Empleado, [Numero]+CDate("01-10-10")-1 AS FechaVirtual, EmpleadosTurnos.Grupo, fturnofecha([fechavirtual],"Mañana,Tarde,Noche, Libre",CDate("04-01-07"),[Grupo]-1) AS Turno
    FROM Numeros, EmpleadosTurnos
    WHERE ((([Numero]+CDate("01-10-10")-1)<=CVDate("31-10-10")))
    ORDER BY EmpleadosTurnos.Empleado, [Numero]+CDate("01-10-10")-1;

    Y el resultado, resumiendo, porque serían 372 filas, mostraría algo parecido a lo siguiente completado hasta mostrar todos los empleados con todas las fechas de octubre del 2010 y el turno de cada fecha

     

    qTurnosVirtuales
    Empleado FechaVirtual Grupo Turno
    Andrés 01/10/10 1 Noche
    Andrés 02/10/10 1 Libre
    Andrés 03/10/10 1 Mañana
    Andrés 04/10/10 1 Tarde
    Andrés 05/10/10 1 Noche
    Andrés 06/10/10 1 Libre
    Andrés 07/10/10 1 Mañana
    Andrés 08/10/10 1 Tarde
    Andrés 09/10/10 1 Noche
    Andrés 10/10/10 1 Libre
    Andrés 27/10/10 1 Mañana
    Andrés 28/10/10 1 Tarde
    Andrés 29/10/10 1 Noche
    Andrés 30/10/10 1 Libre
    Andrés 31/10/10 1 Mañana
    Bartolomé 01/10/10 2 Tarde
    Bartolomé 02/10/10 2 Noche
    Bartolomé 03/10/10 2 Libre
    Bartolomé 04/10/10 2 Mañana
    Bartolomé 05/10/10 2 Tarde
    Bartolomé 06/10/10 2 Noche
    Bartolomé 07/10/10 2 Libre
    ..

     

    La consulta la podemos personalizar para pasarle las fechas inicial y final del periodo como parámetros y también podemos tomarla como puento de partida para una consulta de datos anexado o de actualización que para cada día del año y cada empleado deje anotado el turno de trabajo. Aunque no es necesario volcar los datos en una tabla, pues podemos utilizar la consulta con cualquier periodo de fechas, si resulta muy conveniente, pues además de los distintos turnos pueden darse otras situaciones, como bajas, vacaciones, cambios de turno que hacen necesario trabajar con datos guardados.

    A menudo se pretende realizar una planilla que refleje los trabajadores que entran en cada turno cada día, utilizando los días como encabezado de columna.

     

     

    qTurnosVirtuales_Tabla de referencias cruzadas
    Empleado Grupo 01/10/10 02/10/10 25/10/10 26/10/10 27/10/10 28/10/10 29/10/10 30/10/10 31/10/10
    Andrés 1 Noche Libre Noche Libre Mañana Tarde Noche Libre Mañana
    Jaime 1 Noche Libre Noche Libre Mañana Tarde Noche Libre Mañana
    Pedro 1 Noche Libre Noche Libre Mañana Tarde Noche Libre Mañana
    Bartolomé 2 Tarde Noche Tarde Noche Libre Mañana Tarde Noche Libre
    Felipe 2 Tarde Noche Tarde Noche Libre Mañana Tarde Noche Libre
    Juan 2 Tarde Noche Tarde Noche Libre Mañana Tarde Noche Libre
    Mateo 3 Mañana Tarde Mañana Tarde Noche Libre Mañana Tarde Noche
    Santiago 3 Mañana Tarde Mañana Tarde Noche Libre Mañana Tarde Noche
    Tomás 3 Mañana Tarde Mañana Tarde Noche Libre Mañana Tarde Noche
    Judas 4 Libre Mañana Libre Mañana Tarde Noche Libre Mañana Tarde
    Matías 4 Libre Mañana Libre Mañana Tarde Noche Libre Mañana Tarde
    Simón 4 Libre Mañana Libre Mañana Tarde Noche Libre Mañana Tarde

     

    Evidentemente es fácil de hacer con una consulta de referencias cruzadas.

     

    image

     

    El texto SQL sería:

    TRANSFORM First(qTurnosVirtuales.Turno) AS PrimeroDeTurno
    SELECT qTurnosVirtuales.Empleado, qTurnosVirtuales.Grupo
    FROM qTurnosVirtuales
    GROUP BY qTurnosVirtuales.Empleado, qTurnosVirtuales.Grupo
    ORDER BY qTurnosVirtuales.Grupo
    PIVOT qTurnosVirtuales.FechaVirtual;

     

    Si nuestra consulta de referencias cruzadas la convertimos en un informe y usamos formato condicional, el resultado es la mar de vistoso (o la mar de hortera, según gustos). En el ejemplo le hemos quitado columnas para que nos quepa bien en la página.

    image

     

    En fin, se trata de una serie de ideas que pueden servir a alguien que debe plantearse la gestión de turnos de trabajo, pero no pretende ser ni “la solución” ni la forma canónica de plantearlo.

    Publicado por Chea con 8 comment(s)
    Archivado en: ,

    Hola a Office 365, adiós a Office Live Small Business

    Al tiempo que se anunciaba el lanzamiento de la beta de Office 365, el Access Team Blog anunciaba que Office 365 incluirá Access Services y, por tanto, Access web Databases y en la comunidad online de Office Live Small Business (OLSB) daban la noticia de que, ante la llegada de Office 365, OLSB desaperecerá en el plazo de un año.

    Habrá un periodo de transición para migrar de OLSB a Office 365 para pequeñas empresas a finales del 2011 o principios del siguiente, con un periodo gratuito de tres meses, pero después habrá que pagar por el servicio. Hay quien dice que a Manolete le mató el toro porque la corrida era de beneficencia y eso mismo ha debido pensar Microsoft. Para quienes migren desde OLSB, el precio parece que no será muy caro, unos 6$ al mes por cuenta, muy poco para una empresa incluso pequeña, pero bastante para sustituir a algo que se prometió como gratis de por vida (claro, de la vida del producto que ahora eliminan Triste).

    Para quienes queremos jugar con las posibilidades de las aplicaciones web de Access 2010 o utilizar Sharepoint como back end de nuestras aplicaciones Access de escritorio, el anuncio es realmente prometedor pues el rendimiento Access Services es muy superior al de las listas vinculadas en Sharepoint 2007, por el volumen de datos que es capaz de gestionar sin problemas y por la velocidad notablemente superior y porque, sencillamente, las aplicaciones web de Access 2010 son impensables en Sharepoint 2007. Hasta ahora, lo más barato con Access Services era AccessHosting a partir de 19$ mes el pack developer, por lo que el precio de Office 365 para pequeñas empresas resulta muy competitivo, menos de un tercio, y eso sin contar con que los Access Services son la parte más pequeña del paquete de Office 365, que incluye Office Professional, Exchange Online, Sharepoint Online y Lync Online.

    Para quienes hemos utilizado OLSB precisamente porque era un recurso gratuito de por vida, no deja de ser una faena. Algunas aplicaciones de escritorio en Access 2007 podían compartir unos pocos datos en la web de forma muy sencilla y sin coste alojándolos en OLSB. Algunos nos hemos entretenido en diseñar en OLSB páginas web sencillas para otros que no se pueden permitir un coste superior a gratis, el pequeño negocio del amigo que empieza, una actividad del colegio, etc., precisamente porque OLSB ofrecía un alojamiento gratuito con una calidad aceptable. Unos y otros, en ambos casos yo, ahora se ven decepcionados, pues no necesitaban nada más y ahora se lo quitan, ofreciéndoles a cambio, de pago, lo que no han pedido.

    Así que mi entusiasmo por lo uno frena mi decepción por lo otro y viceversa y, de momento, ando más bien perplejo.

    Tengo muy claro que contrataré Office 365 para la pequeña empresa por utilizar los Access Services y para seguir alojando mi página, pero para aquellas cosas en las que utilizaba OLSB por ser gratuito, empezaré a irme pasando a Google Sites ¿Habrá valorado Microsoft que esta última decisión será la lógica para miles de usuarios de OLSB?

    Algunas joyas escondidas en Access 2010

    Access Services

    Estoy impresionado. He aprovechado la oferta de Access Hosting que el otro día anunciaba el Access Team Blog para abrir una cuenta “trial” de Access Services. He probado a crear y subir una aplicación web hecha con una plantilla de Access 2010 y, como era de esperar, ha funcionado a la perfección.

    Las aplicaciones web funcionando en Sharepoint 2010 son la novedad estrella de Access 2010, sin embargo, lo que me ha impresionado ha sido algo mucho más simple: He subido una sencilla aplicación de escritorio y funciona de maravilla, a una velocidad equiparabla a la de cualquier aplicación web; naturalmente, no se ejecuta en Sharepoint, sino que lo usa de Back End, pero hace mucho más, pues no sólo aloja y sirve datos, sino que también guarda todos los demás objetos de la aplicación.

    Navegando por el sitio creado en Sharepoint para nuestra aplicación, tenemos una opción para modificar la base de datos:

    image

    Pinchas en ella y, en pocos minutos, se ha descargado una copia de la aplicación perfectamente operativa que trabaja con los datos alojados en el sitio. La siguiente vez que pinches, sólo tardará un poco más de lo que tardarías en abrir la aplicación en el escritorio. Para conseguir esto, durante el proceso de subida a Access Services se han ido sincronizando no sólo tablas y consultas, sino todos los objetos; así, cuando realizamos una modificación en nuestra BD, en cualquier objeto, no sólo en los datos, el cambio se realiza también en el sitio Sharepoint, de manera que siempre está disponible desde la web la última versión. Una maravilla.

    Como la opción para modificar la base de datos es un acceso directo, podemos copiarlo y usarlo directamente o, remitirlo a otros  usuarios, en vez de andar navegando por el sitio.

    Esto me ha reconciliado con la proyección a la nube de Access 2010. Las listas vinculadas en Access 2007 son una opción interesante, pero bastante más limitada y lenta de lo que se prometía, para colmo, lo que tuviéramos hecho con listas vinculadas ya no nos vale ahora para los Access Services, de manera que no estaba muy seguro de que el premio mereciera el trabajo de aprender a manejar las macros, el formulario de navegación, etc.. Ahora, sólo por esta posibilidad de tener los datos en la web de manera fácil u operativa, ya merece la pena subirlos y, ya puestos….

    Access 2010 viene con grandes novedades de las que, desde el inicio de la Vista Previa Técnica, el Access Team Blog nos viene poniendo al día. Eventos y macros de datos, campos calculados en tablas, aplicaciones en la web, formularios de navegación, control WebBrowser vinculado a datos… son serias innovaciones que cambiarán la forma de trabajar con Access y que parecen todas orientadas a las aplicaciones web. Había dejado para cuando estuviera con más ánimo el ir probando todo lo referente a esas novedades estrella,  pero, mientras, he ido descubriendo cosas más terrenales, mejoras sobre cosas que ya se venían usando, que o no se mencionan en los blogs, o lo hacen de pasada, pero que por sí solas ya justificarían una nueva versión. A esas pequeñas joyas voy a referirme ahora.

    Formato condicional

    Casi parece que no ha cambiado en nada, a simple vista, los asistentes parecen los mismos, pero uno se pone a meter hasta cuarenta y nueve formatos condicionales y no se queja, mientras que antes venía limitado a tres.

    image

    Si, además, nos fijamos bien en el asistente, vemos que tiene un desplegable, “Seleccionar tipo de regla” con una opción, comparar con otros registros.

    image image

     

    Seleccionamos entre distintas opciones, y el resultado es una barra proporcional a los distintos valores de nuestro formulario o informe, un estilo a lo que ya existía en Excel

    image

    No sólo funciona con informes y formularios continuos. También se pueden usar, y funciona correctamente, en formularios estándar

    image

    Texto enriquecido en subformularios

    Aunque parece que lo corregirán en breve, en Access 2007 no era posible editar el texto enriquecido en los subformularios. Ahora sí, en cualquier lugar, incluso en subformulario continuos, aunque sean en vista Hoja de Datos.

    image

    SubInformes en formularios

    Estamos tan acostumbrados a que no se puedan meter SubInformes en formularios que hasta cuesta pensar en la utilidad que pueden tener.

    Por ejemplo, haciendo un refrito, en muy pocos minutos hemos convertido el  informe “Listín de teléfonos” en panel de navegación del formulario “Detalle de clientes” de la BD “NorthWind”.

    image

    Se me ocurre que navegar de manera semejante por un Diario o un Libro Mayor correctamente formateados en un subinforme, con sus Totales por asiento, Sumas Contínuas, etc., dentro de un formulario para editar los asientos, puede resultar muy vistoso, incluso muy útil, y realmente fácil de hacer.

    Se trata de una “Vista Informe” por lo que faltan las opciones de impresión que tiene la “Vista Previa”, pero, a cambio, podemos disponer de eventos (Clic, doble-clic…) filtros, etc.. Nada cuesta poner un botón en el informe o en el Ribbon para que nos muestre una “vista previa”

    En otros casos, como corresponde a un SubInforme, está vinculado al Formulario principal por las propiedades LinkChildFields y LinkMasterFields, de manera que, al cambiar de registro el formulario, el subinforme a la vista se filtra según estos campos.

    También existe la posibilidad contraria, la de mostrar subformularios en informes. El subformulario no es editable por lo que no le veo ninguna ventaja sobre un subinforme en vista informe, salvo ahorrarse el trabajo de crearlo, pero seguro que, entre las muchísimas barrabasadas que se harán con esta posibilidad, alguien le encontrará un uso brillante.

    Plantilla de ajuste de controles

    Se trata de que los controles se ajustan a una plantilla o tabla, como en las hojas de estilo en el diseño web, en la que podemos añadir o quitar filas y columnas, cambiar alto y ancho de celdas, dividir éstas o combinar varias entre sí.

    Access 2007 ya había aportado algo a la colocación de los controles, agrupándolos en formato tabular o apilado, que sigue siendo posible, pero, aunque era una forma muy rápida de poner cierto orden visual, no era una solución muy lograda, ya que todos los controles apilados en el mismo grupo, tenían el mismo ancho.

    En Access 2010 cuando agrupamos controles, de forma apilada o tabular, podemos moverlos a un lado, encima o debajo, como si añadieramos filas y columnas a una tabla de word; de la misma manera, podemos combinar o dividir esas celdas y los controles que coloquemos se ajustarán a ellas. Es como si debajo de los controles hubiera una tabla o plantilla invisible. Verla es fácil utilizando otra de las novedades de Access 2010, las líneas de división también en formularios.

    image image image imageimage

    Botones con formas, colores y relieves

    Los botones, incluidos los de los TabControls, pueden tener distintas formas colores y texturas. Como muestra, unos pocos:

    image

     

    El botón “Estilos rápidos” muestra una galería rápida, pero también podemos combinar “Cambiar forma”, “Relleno de forma”, “Contorno de forma” y “Efectos de forma” para conseguir un montón de posibilidades.

    imageimage image image image image imageimage

         

    Las formas y colores de los botones no quitan para que puedan seguir teniendo imágenes y pie de imagen, por lo que podemos conseguir unos botones resultados muy vistosos en los que, además, se sigue visualizando el efecto de apretar el botón:

    image

    Los botones con formas y colores se ven afectados por el “Tema” que hayamos elegido. Al cambiar de tema, cambia la galería de “Estilos rápidos” y, si ya lo hemos creado, también cambia la apariencia de nuestro botón.

    image image

     

    Si queremos cambiar la imagen de un viejo formulario Access 2007, de entrada puede que no tengamos habilitadas estas opciones. Se soluciona fácilmente cambiando la propiedad “Usar tema” del control.

    Si nuestra aplicación es en formato mdb, no existe esta propiedad y, por tanto, tendremos que conformarnos con los botones tradicionales.

     

    Generador de expresiones

    Cambia la interface del generador de expresiones.

    image

    Es más completa y con grafía que diferencia los objetos, pero lo más novedoso es que aplica el “intellisense” en aquellas propiedades en que se puede usar el generador.

    image 

     

    Imágenes compartidas

    Aunque ya en Access 2007 las imágenes se guardan en el tamaño del formato original, no deja de ser un desperdicio de espacio repetir la misma imagen, por ejemplo el logotipo de la empresa, en cada formulario o informe. Aunque había soluciones, ésta síque es sencilla de manejar.

    image

    Una vez que para una imagen en la propiedad “Tipo de imagen” hemos elegido “Compartidas”, queda guardada y a disposición de los demás objetos de la aplicación en la galería que se despliega al pulsar el botón “Insertar imagen”.

    image 

    Las imágenes compartidas se guardan como datos adjuntos en la tabla MSysResources, de manera que sólo ocupan espacio una sóla vez. Contiuará existiendo en nuestra BD, y ocupando ese espacio, mientras no la borremos de aquí. Si queremos cambiar el anagrama de nuestra empresa en todos los formularios e informes, basta con que lo cambiemos aquí.

     

    Título en la barra de navegación

    Un pequeño detalle, casi insignificante, pero que refleja cómo en Access 2010 nos encontramos con mejoras a cada paso. Con la propiedad “Título de navegación” podemos indicar qué queremos que ponga en la barra de navegación en vez de “Registro”. Sin pasarse, eh, que el espacio reservado para ello no aumente ;-)

     

    imageimage

     

     

    Publicado por Chea con 15 comment(s)
    Archivado en:

    Magia Potagia II: El desenlace

    En el capítulo anterior habíamos usado ingredientes mágicos de Access 2007 para conseguir crear una serie de calendarios sin apenas usar código. Hoy se trata de licenciarnos como aprendices de brujo haciendo que la magia trabaje para nosotros resolviendo problemas de la vida real.

    Necesitaba crear un subinforme que, para cada uno de los elementos del informe principal, me mostrara un conjunto de fechas representadas en tantos calendarios mensuales como fueran necesarios para mostrar todo el periodo entre la primera y la última. Más o menos, como en las imágenes.

     image     image

    Evidentemente, ya lo he hecho y el caso real lleva tiempo funcionando correctamente. Hay una demo, la de las imágenes, que se puede descargar en Access siglo XXI.

    La magia en la vida real tiene un problema: que no hay quien entienda cómo funciona. Lo mágico de estos calendarios es que apenas usan código, pero el código se puede leer, está estructurado, se autodocumenta; es como un plano de la aplicación. Sin código es muy difícil ver cómo está hecho algo, tanto que, para que el que quiera pueda adaptar mi ejemplo, he tenido que incluir un pequeño asistente.

    No obstante, voy a intentar explicar cómo está construido, aunque me temo que será bastante pesado. Cuando empieces a aburrirte, salta directamente al último punto, al desenlace.

    El punto de partida es una tabla con un campo de fecha y un campo ID numérico, en este caso de usuario, es decir, con un conjunto de fechas, que son las que queremos mostrar en calendario, para cada usuario.

    image

    El idEmpleado es númerico y si se muestran nombres en la imágen es porque uso un campo de búsqueda.

    En el ejemplo del capítulo anterior utilizaba una tabla tipo Calendar, con campos StartDate y EndDate, y mediante una consulta, la convertía en un resultado parecido a éste. Sin embargo, aunque no lo decía, trabajar directamente con la consulta afectaba mucho al rendimiento y sería preferible volcar los resultados en una tabla temporal.

    Obtener todas las fechas de cualquier año

    El primero de los ingredientes mágicos , es una tabla Numeros, con un campo Num con número consecutivos. La mía tiene poco más de mil.

    image

    A partir de esta tabla, mediante una consulta construimos todas las fechas que se corresponden con las del calendario “de fondo”

    SELECT [Numero]+DateSerial(nz(TempVars!jbAnyoInicial,Year(Date())),1,1)-1 AS Fecha, Format([Numero]+DateSerial(nz(TempVars!jbAnyoInicial,Year(Date())),1,1)-1,"dddd") AS Dsemana, (Month([Numero]+DateSerial(nz(TempVars!jbAnyoInicial,Year(Date())),1,1)-1)) AS Mes, (1+(([Numero]+DateSerial(nz(TempVars!jbAnyoInicial,Year(Date())),1,1)-1-2)\7)) AS Fila, Year([Numero]+DateSerial(nz(TempVars!jbAnyoInicial,Year(Date())),1,1)-1) AS Anyo
    FROM Numeros
    WHERE (((Year([Numero]+DateSerial(nz([TempVars]![jbAnyoInicial],Year(Date())),1,1)-1))=nz([TempVars]![jbAnyoInicial],Year(Date()))));

    Mediante el procedimiento de sumar al campo Num una fecha inicial menos uno y hacer que sea menor o igual que una fecha final, podemos obtener cualquier rango de fechas que no supere el total de registros de nuestra tabla Numeros. Pero en el informe podemos superar este límite usando otro de los ingredientes mágicos de Access 2007, TempVars. Al utilizar una variable TempVars en el cálculo de la fecha inicial, podemos cambiar ésta sobre la marcha en el informe de manera que la consulta se vaya recalculando las veces que sea necesario. Ya nos pararemos en ello más adelante; de momento, guardamos esa consulta como jbFechasVirtuales y los resultados sería algo así:

    image

     

    Resaltar los festivos de cada empleado

    Sobre ese “calendario de fondo” se trata de resaltar las fechas de nuestra tabla tblFechasEmpleados usando el tercer ingrediente mágico de A2007, el formato de texto enriquecido. Comparamos tabla y consulta y en las fechas coincidentes añadimos las etiquetas html necesarias para mostrar el resultado resaltado, en nuestro caso en rojo, tal como habíamos visto en el capítulo anterior.

    Sin embargo, antes vamos a anticiparnos con un problema con el que nos toparíamos más adelante en el caso real. El objetivo es presentar los calendarios como subinformes, pero si construimos y formateamos como calendarios todo el conjunto de datos, al vincular por un campo ID informe y subinforme y, por tanto, filtrar los datos de éste, destrozamos el formato de calendario que hemos dado a todo el conjunto. En resumen, que debemos dar forma de calendario a cada subconjunto en vez de filtrar el conjunto de datos total y para ello la mejor solución que se me ha ocurrido es volver a usar TempVars de manera que, cambiando sobre la marcha la variable TempVars!jbID cada vez que cambie el ID del informe principal se recalcula la consulta en la que se basa el subinforme calendario filtrándose por ese ID.

    Así, en vez de usar directamente la tabla tblFechasEmpleados, usaré la consulta que he llamado jbqFestivosVirtuales y que tiene el siguiente SQL.

    SELECT tblFechasEmpleados.idEmpleado, tblFechasEmpleados.Fecha
    FROM tblFechasEmpleados
    WHERE (((tblFechasEmpleados.idEmpleado)=[TempVars]![jbID]));

    Ahora ya podemos juntar las dos últimas consultas para resaltar con formato de texto enriquecido los festivos de los empleados.

    SELECT jbqFechasVirtuales.*, IIf(Not IsNull([jbqFestivosFiltrados].Fecha),"<font color=red>" & Day(jbqFechasVirtuales.fecha) & " </font>",Day(jbqFechasVirtuales.fecha)) AS Expr1
    FROM jbqFechasVirtuales LEFT JOIN jbqFestivosFiltrados ON jbqFechasVirtuales.Fecha = jbqFestivosFiltrados.Fecha
    ORDER BY IIf(Not IsNull([jbqFestivosFiltrados].Fecha),"<font color=red>" & Day(jbqFechasVirtuales.fecha) & " </font>",Day(jbqFechasVirtuales.fecha));

    Guardamos la consulta como jbFechasyFestivosVirtuales y el resultado es como en la imagen siguiente (hasta 365 días) donde destaca un campo que en ocasiones pone algo como <font color=red>10</font> con el que más adelante conseguirems que el número 10 se muestre en rojo.

    image

     

     

    Ya tenemos una secuencia con todas las fechas del año para un determinado usuario con los festivos formateados para que más adelante se muestren en rojo. Para que se parezca a un calendario falta ordenarlo en columnas por días de la semana.

    Dar a los datos forma de calendario

    Ya habíamos visto en el capítulo anterior que para ordenar en columnas por días de la semana una secuencia de fechas podemos usar una Consulta de Referencias Cruzadas que voy a llamar jbqCalendarioFormateado y que será el origen de datos del subinforme calendario:

    TRANSFORM Min([jbqFechasyFestivosVirtuales].Expr1) AS MínDeExpr1
    SELECT [jbqFechasyFestivosVirtuales].Mes, [jbqFechasyFestivosVirtuales].Anyo, [jbqFechasyFestivosVirtuales].Fila
    FROM jbqFechasyFestivosVirtuales
    GROUP BY [jbqFechasyFestivosVirtuales].Mes, [jbqFechasyFestivosVirtuales].Anyo, [jbqFechasyFestivosVirtuales].Fila
    PIVOT [jbqFechasyFestivosVirtuales].Dsemana In ("lunes","martes","miércoles","jueves","viernes","sábado","domingo");

    El resultado, algo parecido a esto:

    image

    El subinforme calendario

    Es pequeñito, para que me quepan más calendarios en una hoja, que hay que ahorrar papel,

    image

    El Origen de Datos es la consulta jbaCalendarioFormateado y el truco principal es que los campos Lunes, Martes, Miércoles … están ocultos (en la imagen en amarillo) y sobre ellos se superponen sendos campos calculados dLunes, dMartes, dMiércoles… que tienen por origen el campo que está debajo. Al ser campos calculados, pueden tener formato de Texto enriquecido, que no es posible en campos de texto, de manera que el campo que antes habíamos construido con etiquetas html del estilo <font color=red>10</font>, ahora se mostrá como 10.

    También están ocultos los campos Mes y Anyo, que nos van a servir para vincular con el subinforme MesesAnyos.

    Este subinforme no tiene ni una sola línea de código.

    El subinforme Meses-Año

    El subinforme anterior muestra un solo calendario, pero necesitamos que nos muestre tantos calendarios como sean necesarios en el subinforme. Por eso lo incrustamos en otro subinforme Meses-Año que tiene todos los meses necesarios para mostrar las fechas de cada empleado.

    image

    El origen del subinforme es la consulta jbqMesesAnyos y tiene por objetivo obtener tantos registros identificados por mes y año como sean necesarios para mostrar todas las fechas de un empleado:

    SELECT ((([Numero]-1)\12)) AS Fila, 1+([Numero]-1) Mod 12 AS numMes, MonthName([NumMes]) AS NombreMes, (([Numero]-1)\12)+Year(DMin("[Fecha]","jbqFestivosFiltrados")) AS Anyo, DateSerial(((([Numero]-1)\12))+Year(nz(DMin("[Fecha]","jbqFestivosFiltrados"))),1+([Numero]-1) Mod (12),1) AS Expr1, [TempVars]![jbID] AS jbID
    FROM Numeros
    WHERE (((DateSerial(((([Numero]-1)\12))+Year(nz(DMin("[Fecha]","jbqFestivosFiltrados"))),1+([Numero]-1) Mod (12),1)) Between nz(DMin("[Fecha]","jbqFestivosFiltrados"))-30 And nz(DMax("[Fecha]","jbqFestivosFiltrados")) And (DateSerial(((([Numero]-1)\12))+Year(nz(DMin("[Fecha]","jbqFestivosFiltrados"))),1+([Numero]-1) Mod (12),1))>0));

    image

    La consultita se las trae, pero, resumiendo, obtiene los meses y año del subconjunto de fechas de un empleado a partir de la tabla Numeros y usando como filtro las fechas mayor y menor de la consulta jbqFestivosFiltrados. Además, obtiene el campo jbID, por el que se vincula con el informe principal, de la Variable Temporal TempVars!jbID.

    En la seccion detalle tenemos el subinforme jbSubRptCalendarioFormateado y, cada vez que se formatea esa sección, cambiamos mediante código la variable temporal TempVars!jbAnyoInicial que, como vimos se usaba en la primera consulta, jbFechasVirtuales, de manera que al cambiarse, esto sí que es magia, se se recalcula toda la consulta y se refresca el subinforme jbSubRptCalendarioFormateado.

    Este es el todo código del subinforme:

    Option Compare Database
    Option Explicit
    
    Private Sub Detalle_Format(Cancel As Integer, FormatCount As Integer)
    TempVars!jbAnyoInicial = Me.Anyo.Value
    Me.SubRptCalendario.Requery
    End Sub
    
    Private Sub Report_Close()
    TempVars.Remove ("jbAnyoInicial")
    End Sub
    
    
    Private Sub Report_Open(Cancel As Integer)
    TempVars!jbAnyoInicial = Year(DMin("Fecha", "jbqFestivosFiltrados"))
    End Sub

    El informe principal

    En el informe principal incrustamos el subinforme jbSubRptMesesAnyo y necesitamos un campo ID por el que vincular con éste.

    image

    Es necesario que tengamos una agrupación por ese campo ID y que ese campo se encuentre en la sección encabezado de ese grupo.

    ¿Por qué? Por que es la única forma que he conseguido que me funcione el abracadabrante rizado del rizo:

    Los campos por los que se vinculan informe y subinforme son respectivamente id y jbid.

    image

    En el evento Format de la sección EncabezadoDelGrupo0 le doy a la variable temporal TempVars!jbID el valor de ese campo ID y, como resulta que el campo jbID del subinforme lo toma de esa variable temporal, estoy asignando sobre la marcha y mediante código en el informe principal el valor del campo del subinforme por el que éste se vincula con el informe principal ¡Con dos c…!

    Para más magia potagia, cambiar por código TempVars!jbID, como forma parte de la primera consulta, jbFechasVirtuales, hace que ésta se refresque y haga los cálculos sólo para el empleado del ID. Como también está en la consulta jbqFestivosFiltrados y del máximo y mínimo de ésta depende la consulta jbqMesesAnyo, no sólo se restringen los festivos a los del empleado, sino que se recalcula jbqMesesAnyo, que es precisamente el origen del subinforme.

    Todo el código necesario es el siguiente:

    Option Compare Database
    Option Explicit
    
    Private Sub EncabezadoDelGrupo0_Format(Cancel As Integer, FormatCount As Integer)
    TempVars!jbID = Me.ID.Value
    Me.subRptMesesAnyo.Requery
    End Sub
    
    Private Sub Report_Close()
    TempVars.Remove ("jbID")
    End Sub
    
    Private Sub Report_Open(Cancel As Integer)
    TempVars!jbID = DFirst("id", Me.RecordSource)
    End Sub

    El desenlace

    Yo ya advertí que iba a ser aburrido. Resumiendo, usando TempVars y consultas basadas en una tabla Numeros, se pueden hacer cosas inimaginables de otra manera.

    Es magia empaquetada. No es necesario entenderla toda ni seguir complicados rituales para usarla en una aplicación propia pues, de todos los objetos y consultas que utiliza sólo es necesario cambiar el informe principal y la consulta jbqFestivosFiltrados. En Access siglo XXI puedes descargarte la aplicación de ejemplo que, además, tiene un asistente elemental para modificar esa consulta y el código del informe principal.

    ¿De dónde he sacado yo estas cosas de mágia? Ni idea, supongo que andaré hechizado, aunque algo de inspiración se me habrá pegado de Ramón Poch o de Julián Sánchez, aficionados también a jugar con una tabla Numeros.

    Por cierto Dominguín iba a las siete de la mañana… ¡A contarlo!

    Access 2010- Hermenéutica sobre un volcado de pantalla

    Ya tenemos, como un pastel en un escaparate, sin tocar, sin oler, sin probar, sólo ver, las primeras imágenes de Access 2010. Son una parte de un pqueño conjunto de volcados de pantalla sobre Office 2010 que podemos ver en Leaked: Office 2010 Technical Preview screenshots. Es poco lo que se muestra, pero lo imagino delicioso.

    image

    Sobre lo que vemos, unido a algunos comentarios que se han ido desgranando casi desde que salió 2007, podemos hacer un ejercicio de hermenéutica para interpretar qué nos puede traer de nuevo Access 2010, que, por si alguno no lo sabe ya, presentará su Technical Preview, sólo por invitación, junto con el resto del paquete de Office, en julio de este año.

    Sabemos que VBA seguirá existiendo

    Sabemos que funcionará con Windows XP SP3, Vista y Windows 7 y que tendrá versiones de 32 y 64 bits.

    Sabemos que no formará parte de las Office Web Apps.

    Desentrañemos ahora esos volcados de pantalla en busca de nuevos augurios, fijándonos primero en los que muestran distintos Ribbons:

    - Cambia el botón de Office. A muchos no les gustaba por ostentoso; a mí sí. Ahora es más pequeño, discreto y deja de ser redondo, aparentando una pestaña más, aunque destacada (y más fea).

    - El Tab Home apenas muestra diferencias respecto al actual

    - En Create tenemos un nuevo grupo, Templates, y, dentro de él, el botón desplegable Aplication Parts. A cambio, ha desaparecido el botón Plantillas de tablas, que es de suponer que estará incluido en el primero. Por los comentarios de Access Team Blog y sus peticiones a los usuarios para que reportaran los formularios más usados, convenciones de nombre o ejemplos de expresiones, formularios e informes reusables, es de esperar que Access 2010 venga cargado de plantillas de todo tipo, incluido código al estilo del Administrador de fragmentos de código de Visual Studio

    Algo que pasa desapercibido a primera vista es que el botón de Macro, que tiene el mismo icono de antes, ya no se llama Macro a secas, sino Client Macro ¿Significa este calificativo que va a haber también algo como un Server Macro? Después de observar las imágenes de los Ribbons siguientes, yo no lo descartaría.

    - En External Data, en el grupo Import, nos encontramos con un botón Web Service. Ya lo conocíamos; era de lo poco que se había dejado ver de Office 14, también en Ars Technica. Yo apostaría por que está en relación con la Plataform de Servicios Windows Azure, y más en concreto con los SQL Services, aunque no habría que descartar otros. Quizás sólo sea que mi imaginación se deja llevar por mis deseos.

    - Y la gran sorpresa para el final. Bajo el supertab Table Tools, en el Tab Modify Fields, aparece un Ribbon desconocido y sorprendente. Unos pocos botones, como los del grupo Formatting, son previsibles y, al parecer no hacen más que poner en el Ribbon accesos a funcionalidades que ya existían, pero otros son completamente novedosos

    Un botón Calculate Field ¡En el diseño de tabla! y un grupo Table logic, con los botones Create Table Events, desplegable, y Manage Table Events, nos hablan de un nueva dimensión de Access que, si es lo que parece a muchos nos va a dejar con la boca abierta.

    Ya no es cuestión de si un Evento de Tabla se parecerá a un Trigger, o a un Flujo de Trabajo, más fácil, o si será algo distinto y propio de Access, sino, más bien, cómo co…o (córcholis) puede producirse un evento de tabla en un ACCDB si es un simple archivo. Tenemos dos meses hasta la Technical Preview para jugar haciendo conjeturas; por ejemplo, que las tablas con eventos no puedan ser leídas por versiones anteriores del motor de Access (que sería el encargado de disparar los eventos) o, con mucha imaginación, que dispusierámos de un WSS local.

    Los campos calculados en las tablas y los eventos de tablas recuerdan mucho a las listas de Sharepoint, y en nombre Sharepoint se repite y se repite cada vez que leemos algo de Office 2010; que si Groove ahora va a ser Sharepoint Workspace, que si las Office Web Apps estarán integradas en Sharepoint

    ¡Oh! ¡No lo había visto! Volviendo al primero de los volcados de pantalla de Access, el que está antes de los de los Ribbons y se corresponde con la ventana de apertura de Access, disimuladamente, en el primer apartado, Avalaible Template, junto al icono de Blank Database, tenemos el de Blank Web Database. Qué significa esto es pronto para decir, pero, si es lo que parece, promete ser la novedad más importante de Access 2010. Seguro que también tiene que ver con Sharepoint.

     

    Publicado por Chea con 6 comment(s)
    Archivado en:

    Filtros en Access 2007

    Access 2007 incorpora importantes herramientas para facilitar que el usuario pueda realizar filtros complejos sobre formularios, incluso sobre informes, de una manera manera muy sencilla, tanto que uno se pregunta si merece la pena continuar con los procedimientos que, durante años, hemos ido añadiendo a nuestras aplicaciones para filtrar.

    Para muestra vale un botón (el de filtro): 

    image

    image

    Con sólo posicionarnos en un campo de un formulario, en este caso de fecha, y pulsar el botón de Filtro que encontramos en la Cinta de Opciones, Access despliega una abrumadora relación de opciones para filtrar el formulario por el campo en el que nos encontramos.

    Las opciones que se despliegan varían según el campo por el que vamos a filtrar sea numérico, de texto o de fecha.

     

    Varias maneras de acceder a las herramientas de filtro

    Usando el menú contextual

    Pulsando con el botón derecho del ratón sobre un control del formulario, se despliega un menú contextual que, además de las opciones de edición y orden, muestra las de filtro. Son muy parecidas a las que hemos visto antes con el botón de filtro de la cinta de opciones, pero falta el combo multivalor y se añade la opción de filtro por selección, es decir, el valor actual del control como opción de filtro igual, distinto, mayor o menor.

    image

    Dos posibilidades de herramientas de filtro casi iguales, pero distintas. El caso es que el menú contextual no está disponible con la runtime de Access y el Ribbon no es accesible en los formularios emergentes :-(

    Sin embargo, podemos invocar el menú de filtro (el del Ribbon) posicionándonos el cualquier control y usando el código:

    DoCmd.RunCommand acCmdFilterMenu

    Y, si lo queremos que se mueste es el menú contextual, podemos usar:

    CommandBars("Column Filter").ShowPopup

    Usando el Ribbon

    En el grupo de la Cinta de Opciones Ordenar y Filtrar, no sólo tenemos el botón de filtro que veíamos antes. Los botones Selección y Avanzadas despliegan una serie de opciones que ya existían en versiones anteriores de Access.

    imageimageimage

    • Las opciones de Selección ya existían en versiones anteriores, salvo la última, Entre…, también disponible en el menú contextual y en el botón de filtro, bastante novedosa y útil: muestra un cuadro de diálogo con dos cuadros de texto para indicar los límites de un rango por el que filtrar.

    image

    La imagen es para un rango de fechas, pero también existe para un rango de números. No existe para texto, aunque para texto hay otras alternativas.

    • Desplegando el botón Avanzadas encontramos Filtro por formulario y Filtro avanzado/Ordenar. Son viejos conocidos de versiones anteriores; el primero presenta el formulario en una vista especial para introducir criterios y el segungo muestra la cuadrícula QBE para editar el text SQL correspondiente al filtro.

    Son herramientas mas complicadas de usar que las nuevas de 2007, pero siguen siendo necesarias si queremos manejar cierta complejidad.

    Mediante las herramientas del botón de Filtro del Ribbon o del menú contextual, los distintos criterios de filtro se van concatenando con AND sin opciones de usar OR y sin mostrar una vista conjunta de los distintos criterios que se están aplicando. No es necesario elegir entre una y otras, pues se pueden ir complemantando: el filtro que hayamos creado con el menú desplegable podemos editarlo con Filtro por formulario o con Filtro avanzado/Ordenar para hacerlo más complejo.

    Alternar el filtrado

    Al filtrar, se resalta en naranja un nuevo botón poniendo “Filtrado” en la barra de navegación del formulario :

    image

    Pulsando sobre él, se desactiva el filtro y cambia el texto del botón a “Sin filtrar”. Si volvemos a pulsar, se vuelve a activar el filtro y a cambiar el texto. Es decir, el botón sirve para alternar la propiedad FilterOn del formulario.

    image

    Pero aún afina más. Como hemos visto, los criterios de filtro se van acumulando con AND, de manera que un formulario puede estar filtrado por varios criterios; pues bien, ir eliminando selectivamente esos criterios es tan sencillo, como volver a posicionarse sobre el control con el que habíamos filtrado y pulsar de nuevo el botón derecho del ratón, se mostrará entonces una opción en el menú desplegable para quitar ese criterio.

    clip_image001[1]

     

    Filtros en hojas de datos

    Los menús de filtro también están disponibles en las vistas de hojas de datos de tablas y consultas, sin necesidad de que se trata de un subformulario. Además, están directamente accesibles ambos menús de filtro, el contextual y el que veíamos desde el Ribbon, para el primero usando el botón derecho del ratón y, para el segundo, pulsando sobre el pequeño triángulo con un vértice hacia abajo que aparece junto al nombre de cada campo.

    imageimage

    Al aplicar el filtro, junto nombre del campo que hemos usado aparece un minúsculo icono de filtro

    image

    Y, si pulsamos en él se despliega el menú de filtro con, entre otras, la opción para quitar el filtro por ese campo.

    image

    Filtros en informes

    Algo sorprendente en Access 2007 es que los informes también pueden filtrarse dinámicamente de la misma manera que hacemos en un formulario. Como veíamos en un artículo anterior, existen Nuevas vistas de informe en Access 2007 distintas de la Vista Preiliminar.

     image image

    Si abrimos nuestro infrome como Vista Informe, se presenta de una forma peculiar, sin opciones de impresión o vista previa y sin saltos de página, pero con algunas opciones propias de los formularios, como la posibilidad de seleccionar y copiar texto, etiquetas inteligentes o las opciones de filtro, todas las que hemos visto antes para filtrar informe. Una vez filtrado, podemos imprimirlo directamente utilizando Botón de Office | Imprimir o pasar a la Vista Preliminar desde la opción Vistas del Ribbon.

     

    Un poco de código

    Menú de filtro en formularios emergentes

    La cinta de opciones no está disponible si estamos utilizando un formulario emergente, por lo tanto, no podemos usar el menú de filtro propio de esa cinta, salvo que lo hagamos por código:

    Private Sub Filtro_Click()
    Dim ctrl As Control
       On Error GoTo Filtro_Click_Error
    
    Set ctrl = Screen.PreviousControl
    ctrl.SetFocus
    DoCmd.RunCommand acCmdFilterMenu
    
       On Error GoTo 0
       Exit Sub
    
    Filtro_Click_Error:
    
        MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Filtro_Click of Documento VBA Form_Detalles de empleados"
    
    End Sub

     

    clip_image001image

    El mismo botón sirve para filtrar en el formulario principal o en un subformulario. Sólo es cuestión de posicionarse previamente en el campo por el que queremos filtrar

     

    Aprender de Access

    Resulta un ejercicio interesante ver cómo los menús de filtro de Access construyen los criterios de filtro. Para ello, en un formulario para ensayos añadimos un cuadro de texto txtFiltro y el siguiente código:

    Private Sub Form_ApplyFilter(Cancel As Integer, ApplyType As Integer)
    Me.txtFiltro = Me.Filter
    End Sub

    De esta manera, cada vez que apliquemos un filtro, veremos el criterio que se ha generado.

    image

    (([Productos].[Categoría]="Bebidas")) 
    AND ([Productos].[Costo estándar]<=13.5)

     

    Observamos que, a medida que vamos filtrando, se van añadiendo cadenas de filtro unidas por AND, lo cual era perfectamente previsible. Claro, que sólo cabe unir los distintos criterior con AND y a lo mejor nos interesa unir alguno con OR. Con un par de líneas nás de código podemos hacer que, si editamos el texto que nos muestra el filtro, éste se convierta en el nuevo filtro de nuestro formulario.

    Private Sub txtFiltro_AfterUpdate()
    Me.Filter = Me.txtFiltro
    Me.FilterOn = True
    End Sub

     

    Desplegable Multivalor para filtrar

    Resulta curioso observar cómo la sintáxis va cambiando según la cantidad y proporción de elementos que vayamos seleccionando para filtrar en el desplegable multivalor del menú de filtro.

    image image image image

    Las distintas selecciones provocan distintos criterios de filtro según se haya seleccionado un sólo elemento o unos pocos, o todos menos uno o todos menus unos pocos. Los criterios generados son las siguientes.

     

    ([Empleados ampliados].[Apellidos]="Acevedo")
    ([Empleados ampliados].[Apellidos] In ("Acevedo","Bonifaz","Jesús Cuesta"))
    ([Empleados ampliados].[Apellidos]<>"Chaves" Or [Empleados ampliados].[Apellidos] IS Null)
    ([Empleados ampliados].[Apellidos] Not In ("Chaves","Jesús Cuesta") Or [Empleados ampliados].[Apellidos] IS Null)
     
    Filtrando campos multivalor

    Nada de todo esto nos resulta novedoso, así que vamos a ver cómo se generan los filtros con campos multivalor, específicos de Access 2007.

    image

    En el menú contextual de filtro elegimos el valor actual de nuestro campo multivalor: “Contiene Proveedor D; Proveedor F”. Filtramos y vemos que el filtro generado es el siguiente:

    ([Lookup_Supplier IDs].[Compañía]="Proveedor D" 
    AND [Lookup_Supplier IDs].[Compañía]="Proveedor F")

     

    LookUP

    Es decir, que ha concantenado con AND los distintos valores de nuestro campo multivalor, algo previsible. Sin embargo, si nos fijamos, nuestro campo se llama [Supplier IDs] mientras que el nombre que aparece es [Lookup_Suplier IDs], o sea, lo mismo pero con un “LookUp_” delante. Eso me suena de versiones anteriores utilizando “Filtro por formulario”…

    Utilizando la herramienta de filtro “Avanzadas | Filtro avanzado/Ordenar…” vamos a verlo por dentro.

    Esa herramienta se encuentra en el Ribbon y, como estamos en un formulario emergente, no podemos acceder a ella, de manera que, o bien quitamos la propiedad Emergente al formulario y seleccionamos la opción correspondiente en el Ribbon…

    image

    O bien añadimos un botón que llame a la herramienta mediante código:

    Private Sub FiltroAvanzado_Click()
    DoCmd.RunCommand acCmdAdvancedFilterSort
    End Sub

    De cualquiera de las dos maneras el resultado será que se muestre el diseñador gráfico de consultas con el filtro actual

    image

    Observamos que a la tabla origen de nuestro formulario, Productos, se le ha relacionado una tabla o consulta llamada Lookup_Supplier IDs y que coinciden con los campos que se encuentran en el Origen de la Fila del cuadro combinado que usamos para la búsqueda de Id de Proveedores. Recordemos que, para que un campo sea Multivalor, en el diseño de la tabla, en la pestaña “Búsqueda” debemos usar un cuadro combinado con un origen válido y Permitir varios valores.

    Por lo tanto, el filtro con Lookup se usa en los campos multivalor porque son cuadros combinados. La sintáxis, si queremos crear nuestro propios criterios con Lookup_ , sería algo así:

    ([Lookup_NombreControl].[NombreColumnaMostrada]="Criterio filtro")
     
    Usando el operador "=" no deja de ser una chorradita, pues para eso podemos buscar directamente por el campo dependiente. Pero si usamos "Like" o cualquier otro operador en su lugar, la cosa cambia, pues el filtro se hace por la columna mostrada que no está en el origen de datos del formulario filtrado. Evidentemente, esto es una gran ventaja a la hora de construir un filtro.
     
    Si utilizamos esa sintaxis al montar nuestra propia cadena de filtro, funciona perfectamente, añadiéndole potencia de una forma muy sencilla.
     
    En resumen, que es una herramiento muy maja y que funciona muy bien y, sin embargo, no he encontrado nada en la ayuda ni en ningún otro lado. Todo lo que he encontrado, y con ayuda de Emilio Sancha, es este enlace de Allenbrowne que lo usa, para búsquedas, en una aplicación de ejemplo: http://www.allenbrowne.com/AppFindAsUType.html. Prometo que profundizaré en el asunto y publicaré los resultados, aunque todo el mundos sabe ya que, para estas cosas, soy hombre de poca palabra ;-)
     
     

    ApplyFilter

    Entre las novedades en Access 2007 se cita la nueva sintáxis de AppliFilter

    Sintaxis anterior

    Sintaxis nueva

    Sencillamente se le ha añadido un parámetro ControlName y, probando, nos encontramos con que sólo es válido el nombre de un control de subformulario o subinforme, por lo que es fácil deducir que sirve para filtrar directamente el subformulario o subinforme desde el principal.

    Por ejemplo, si queremos filtrar el subformulario Child22, podemos poner en el formulario principal un botón con el siguiente código.

    Private Sub Mibotón_Click()
    DoCmd.ApplyFilter , "Transacción='Compra'", "child22"
    End Sub

    Teniéndolo en mente, esto puede facilitar un poco las cosas en determinadas ocasiones, pero no pasa de ser una insignificancia ¿Para qué se han tomado la molestia? Me imagino que será para facilitar el uso de macros. Las macros ejecutan comandos y, si sólo añadiendo un parámetro, el filtro se puede aplicar a un subformulario, la macro se simplifica notablemente.

    Publicado por Chea con 14 comment(s)
    Archivado en: ,

    Online: Mejora de las aplicaciones Access 22/01/09

     

    Mejora de las aplicaciones Access con SQL Server y Sharepoint

    El evento lo vamos a grabar y lo transmitiremos online, os paso los datos de conexión para que probéis el acceso:

     

    Evento Online 1era parte – sesión mañana

    • Event URL :
    • Evento Online 2da parte – sesión tarde

    • Event URL :
    Publicado por Chea con 3 comment(s)
    Archivado en: ,,

    Access Developer Extensions en español

    En el grupo de noticias de Access, Freddy M. Aragón nos ha informada que ya están disponibles para su descarga las Access Developer Extensions en español.

    Las Developer Extensions añaden una nueva opción Programador al menú de Office que nos permite crear un paquete de instalación y guardar como plantilla.

    Si ya teníamos instalada la versión en inglés, sencillmanente sustituya una por otra.

    image

    Publicado por Chea con 6 comment(s)

    El uso de las Cintas de Opciones (Ribbon) y XML (Lenguaje de Marcado Extensible). RibbonAndXML_01

    Capítulo 1

     La pretensión de este artículo (se incluye demo) es aprender a cambiar de Cinta de Opción (Ribbon) haciendo uso del XML (Lenguaje de Marcado Extensible).

    Sin ánimo de ser exhaustivo la sintaxis es muy similar a la del HTML, es otro lenguaje basado en marcas pero estas son “más estrictas”, contiene propiedades que la hacen que tenga un formato de datos útil para poder ser leído por varios programas e incluso sistemas.

     Las reglas básicas de la especificación XML son las siguientes:

    ·         Sólo se permite un único elemento raíz.

    ·         Deben de existir etiquetas de inicio y fin para cada elemento, <elemento>…</elemento>.

    ·         Es sensible a mayúsculas y minúsculas.

    ·         Los atributos deben estar entrecomillados.

    ·         El texto comentado tiene la siguiente estructura <!-- … -->

    ·         y más, pero dejo a San Google para complementar la información.

    Primeros conceptos del código XML que genera la Cinta de Opciones (Ribbon).

    <customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui>

          <ribbon startFromScratch="true">

    </ribbon>

    </customUI>

     

    La primera etiqueta a utilizar siendo necesaria para las personalizaciones del Ribbon, es también el elemento raíz.

    <customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui">

    La siguiente etiqueta hace referencia al objeto Cinta de Opciones y que cuenta con el atributo que puede tener dos valores true y false. Si su valor es true se quitarán todas las fichas predetermiadas por Office y la personalizada es la que quedará visible. False para no modificar las existentes dejando a la derecha la nueva.

          <ribbon startFromScratch="true">

    Importante conocer que este modo también oculta los comandos que tiene el nuevo menú Office excepto Nuevo, Abrir, Guardar como y Cerrar base de datos, tal como veremos a continuación en la imagen.

    Así de sencillo, con tan sólo una línea hemos modificado toda la Cinta preparándola para usar nuestras propias fichas y grupos.

    El modo startFromScratch está diseñado para ser compatible con las futuras versiones de Office, es decir, si se agrega una nueva ficha o elemento del menú Office, el modo startFromScratch debería ocultarlos automáticamente.

    Por estos motivos es más conveniente usar startFromScratch que no ir ocultando uno a uno los elementos del menú Office y las diferentes fichas y grupos de la Cinta de Opciones.

    Usando estas cinco líneas ya podemos conseguir un efecto significativo en la ventana de Access y que se presenta en la imagen siguiente.

    BotónOffice01

     En caso de querer modificar la visibilidad de los comandos que están incluidos en el Botón de Office hay que usar la etiqueta <officeMenu>

    Para ocultar el comando Nuevo utilizar el identificador de Office "FileNewDatabase" y cambiar su atributo visible a "false"

    <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">

          <ribbon startFromScratch="true">

                 <officeMenu>

                      <button idMso=" FileNewDatabase" visible="false" />

                </officeMenu>

     </ribbon>

    </customUI>

    Analicemos la nueva etiqueta button para entender un poco más como penetrar en este nuevo mundo del XML y del Ribbon.

                      <button idMso="FileNewDatabase" visible="false" />

    Los controles de la Cinta deben de contener como mínimo un elemento que los identifique como únicos y puede ser uno de los siguientes de esta lista:

    ·         idMso: es el identificador de un menú que contenga Office, nombre interno.

    ·         id: Identificador para los controles personalizados y debe de ser único.

    ·         idQ: Identificador cualificado cuyo contenido debe de ser una abreviatura del espacio de nombres.

    El siguiente elemento de la etiqueta es un atributo y que en este caso que se llama visible. Modificando su valor (“entrecomillado”) conseguimos ocultarlo ("false") o hacerlo visible ("true").  

    Para conocer una lista de los nombres internos que ha establecido Microsoft para los comando podemos descargarnos un archivo desde la url siguiente, http://www.microsoft.com/downloads/details.aspx?FamilyID=4329d9e9-4d11-46a5-898d-23e4f331e9ae&displaylang=en. Conseguiremos un montón de libros de Excel organizados por producto, Access, Excel, Outlook, Word, etc.

    Continuamos con los comandos que todavía nos quedan visibles, Abrir, Guardar como y Cerrar base de datos, así como la lista de Documentos recientes.

    Para ocultar el comando Abrir, "FileOpenDatabase" y el botón desplegable Guardar como, "FileSaveAsMenuAccess" podemos usar el siguiente código,

                <button idMso="FileOpenDatabase" visible="false" />

                <splitButton idMso="FileSaveAsMenuAccess" visible="false" />

     

    Por último de esta serie está Cerrar base de datos, "FileCloseDatabase"

                <button idMso="FileCloseDatabase" visible="false" />

    Quedando de este modo si queremos desactivar las cuatro acciones.

          <officeMenu>

                <button idMso="FileNewDatabase" visible="false" />

                <button idMso="FileOpenDatabase" visible="false" />

                <splitButton idMso="FileSaveAsMenuAccess" visible="false" />

                <button idMso="FileCloseDatabase" visible="false" />

          </officeMenu>

    Pero es posible que queramos hacer visible alguno de los comandos para poder imprimir un objeto o mandar por correo electrónico, para ello usaremos las siguientes líneas a continuacion de las anteriores,

                <button idMso="PrintDialogAccess" visible="true"/>

                <button idMso="FileSendAsAttachment" visible="true"/>

    BotónOffice02 

    Doy por finalizado este capítulo primero, espero poder presentar otros sucesivos en los que podremos encontrar el modo de deshabilitar el botón de ayuda, minimizar, restaurar, cerrar e incluso los botones de Opciones de Access y Salir.

    McPegasus, 14/01/2008

    Publicado por McPegasus con 6 comment(s)
    Archivado en: ,,

    Vincular Vistas de Sharepoint desde Access 2007

    Si desde Access 2007, en vez de vincular una Lista de Sharepoint, vinculamos una Vista de ésta, obtenemos claras ventajas, la más evidente limitar el tráfico de datos por la red a los que realmente queremos, pero también otras bastante interesantes, como restringir los datos a los del usuario registrado en el sitio Sharepoint. Sin embargo, los asistente de Access sólo facilitan vincular Listas, debiendo recurrir al método TransferSharePointList para poder conseguirlo.

    La misma ayuda de Access sobre TransferSharePointList nos informa de cómo obtener algunos de los paramátros necesarios:

    Para obtener el identificador GUID de una lista o una vista del sitio de SharePoint puede utilizar el procedimiento siguiente:

    1. Abra la lista de Windows SharePoint Services.
    2. Si no se muestra la vista que desea, haga clic en la flecha desplegable Ver y, a continuación, seleccione la vista deseada.
    3. Haga clic en la flecha desplegable Ver y, a continuación, seleccione Modificar esta vista.

      La dirección de la barra de dirección del explorador contiene los identificadores tanto de la lista como de la vista. El identificador GUID de la lista sigue a List=, y el de la vista sigue a View=. Sin embargo, en la dirección, cada carácter { (abrir llave) se representa mediante la cadena %7B, cada carácter - (guión) mediante la cadena %2D, y cada carácter } (cerrar llave) mediante la cadena %7D. Por ejemplo:

      http://MySite12/_layouts/ViewEdit.aspx?List=%7B2A . . . 7D&View=%7B357B4FE6 . . . 1579B%7D

      Para poder utilizar los identificadores GUID de la dirección como argumentos de esta acción de macro, primero debe reemplazar cada cadena %7B por el carácter {, cada cadena %2D por el carácter - y cada cadena %7D por el carácter }. No incluya el carácter & (y comercial) que sigue a la cadena %7D en el identificador GUID de la lista.

    (Me he permitido recortar la URL que la ayuda usa de ejemplo para que me quepa mejor y porque, en el mismo ejemplo de la ayuda, ya la URL está truncada al final)

    Le faltan unas imágenes que ayuden un poco ¿no? Pues se las ponemos:

    image    image

    Seguir las instrucciones “a mano alzada”, cortando y pegando con el ratón y editando una cadena tan larga, se hace aguantando la respiración y, al final, casi seguro que sale mal (a mí nunca me ha salido bien). Pero para seguir instrucciones al pie de la letra está la programación y en Access contamos con VBA, así que me he hecho el siguiente procedimiento que sólo necesita que copiemos la URL y se la pasemos como cadena de texto:

     

    '---------------------------------------------------------------------------------------
    ' Procedure : VincularVistaWSS
    ' DateTime  : 30/12/08 20:36
    ' Author    : Chea
    ' Purpose   : Vincula una Vista de Sharepoint a partir de una URL
    '             Para vincular una Vista de Sharepoint, debemos, en primer lugar, ir al
    '             sitio de Sharepoint, a la página de configuración de dicha vista y copiar
    '             la URL completa: Esa URL, delimitada por comillas, es el argumento que
    '             debemos pasar a esta función en el parámetro CadenaURL
    '---------------------------------------------------------------------------------------
    '
    Public Sub VincularVistaWSS(CadenaURL As String, Optional NombreTabla)
    
    Dim GUIDList As String
    Dim GUIDView As String
    Dim strSite As String
    Dim v As Variant, V2 As Variant, V3 As Variant, i As Integer
    Dim stTmp As String
    
    ' "Traducimos" caracteres de la cadena URL
    CadenaURL = Replace(CadenaURL, "%7b", "{")
    CadenaURL = Replace(CadenaURL, "%7d", "}")
    CadenaURL = Replace(CadenaURL, "%252E", ".")
    CadenaURL = Replace(CadenaURL, "%252F", "/")
    CadenaURL = Replace(CadenaURL, "%253A", ":")
    CadenaURL = Replace(CadenaURL, "%2D", "-")
    
    ' Obtenemos la segund parte de la URL, desde el aspx?
    v = Split(CadenaURL, "aspx?")
    stTmp = v(1)
    
    'Obtenemos las distintas secciones, que van separadas por "&"
    v = Split(stTmp, "&")
    For i = 0 To UBound(v)
        V2 = Split(v(i), "=")
        If Trim(V2(0)) = "List" Then
            GUIDList = V2(1)
        End If
        If Trim(V2(0)) = "View" Then
            GUIDView = V2(1)
        End If
        If Trim(V2(0)) = "Source" Then
            strSite = V2(1)
            V3 = Split(strSite, "/Lists/")
            strSite = V3(0)
        End If
    Next i
    
    ' Vinculamos la vista
    DoCmd.TransferSharePointList acLinkSharePointList, strSite, GUIDList, GUIDView, NombreTabla
    
    End Sub
    Publicado por Chea con 2 comment(s)
    Archivado en: ,

    Office Live Small Business en español

    Desde hace tiempo está disponible una versión de Office Live Small Business en español, concretamente para México. Los servicios de pago están disponibles para empresas mexicanas, pero la parte “free” es de libre acceso, lo cual es una gran noticia para los hispanohablantes. Yo ya me había acostumbrado a las opciones y menús en lengua hereje, pero sigue sin gustarme que en mi sitio web determinados módulos, por ejemplo formularios, muestren irremediablemente encabezados,expresiones y formatos en inglés. La nueva versión lo traduce todo al español.

    Al entrar en la página de Office Live Samll Business, la opción de país predeterminada es Unites States, pero al lado tenemos un botón “Change”. Al pulsarlo, podemos elegir país entre une breve relación de ellos en la que, sólo recientemente, aparece México.

    imageclip_image001image

     

    Lo había observado hace no sé si una o dos semanas, pero, al cambiar, sólo la página de presentación se mostraba en español y, al entrar en mi sitio, todo seguía en inglés. Probé  con una de esas viejas cuentas de prueba, cancelé la cuenta de Office Live y, con el mismo Live ID creé una nueva, que seguía con todo en inglés.

    Ayer creé una cuenta nueva de Hotmail y, esta vez sí, al crear una cuenta de Office Live en México, todo aparecía en español. Es decir, por cambiarnos de país no se “traduce” nuestro sitio, ni si seguimos usando el Live ID que había usado previamente, pero si creamos una cuenta nueva con un ID sin estrenar, podremos tener nuestro sitio completamente en español.

    No todo son buenas noticias. Intento hacer un backup desde mi sitio en inglés y restaurarlo en español y obtengo un mensaje de error de que no es posible hacer un Restore con una configuración de idioma distinta.

    Publicado por Chea con 5 comment(s)
    Archivado en: ,
    Más artículos Página siguiente >