Documentación

Mantenga las tarjetas al día desde una dirección externa

Una fuente de datos es una dirección que screenRIG lee según un horario. Escribe lo que encuentra en los datos de una tarjeta, de modo que la tarjeta se mantiene al día sin que nadie vuelva a publicar y cada pantalla que muestra la tarjeta recupera el documento cuando cambia. Un calendario de Google, Outlook, iCloud o Nextcloud se convierte en una agenda o un panel de salas, una hoja o una API se convierte en un menú o un panel de indicadores, y una opción predefinida convierte un canal de Slack, las reseñas de Google o un tablón de empleo en una tarjeta.

Cómo funciona una fuente

  • Una fuente, una tarjeta. Una fuente indica la tarjeta que alimenta. Una tarjeta tiene como máximo una fuente, y una organización admite hasta 200 en todos sus proyectos.
  • Se ejecuta según una programación. Una fuente de calendario se sincroniza cada 5 minutos, y cada fuente puede ajustarse entre 1 minuto y 24 horas. Una fuente se ejecuta a los pocos segundos de crearse o cambiarse, y source refresh la sincroniza al instante.
  • Una sincronización fallida nunca modifica la tarjeta. La tarjeta conserva su último documento válido y el estado de la fuente indica qué falló. Los fallos se reintentan solos, con una espera mayor tras cada uno.
  • Solo se escriben los cambios. Cuando una sincronización encuentra lo que la tarjeta ya contiene, no se escribe nada y las pantallas no se activan.
  • Las sincronizaciones son gratuitas. Los documentos cuentan para el almacenamiento de datos de tarjetas como cualquier otra escritura.
  • Las credenciales se guardan cifradas. Una dirección que da acceso se almacena cifrada, se devuelve solo con su esquema y su host, y nunca aparece en un registro, un error ni el registro de eventos del proyecto.
  • Conserva los últimos datos buenos durante el periodo que elijas. Cuando una fuente no puede actualizarse, su tarjeta conserva los últimos datos buenos durante el periodo de caducidad de la fuente (--stale-after, de 1 minuto a 7 días; una fuente de reservas de recinto los conserva 30 minutos); después los datos caducan en todas las pantallas, también las que están sin conexión, y las páginas que dependen de ellos pasan a las páginas siguientes. Sin periodo, los últimos datos buenos permanecen hasta que la fuente vuelve a actualizarse. Consulta Cuando una fuente no puede actualizarse.

Empezar con una opción predefinida

Una opción predefinida es una fuente ya preparada para un servicio que la gente suele poner en una pantalla. Elija una, rellene unos pocos campos, añada la credencial que emite el servicio, y screenRIG crea la fuente y adapta la respuesta a la tarjeta. Las opciones predefinidas son fuentes json, que todos los planes incluyen.

Opción predefinidaTarjetaQué necesita
slack-channelworkplace-posts, message-wallEl ID del canal y el token de bot (xoxb-…) de una aplicación interna de Slack en su espacio de trabajo: en api.slack.com/apps añada los ámbitos channels:history y groups:history, instale la aplicación y luego añádala al canal
bonuslykudosUn token de acceso de Bonusly con recognition:read, que un administrador Global o Tech crea en Company, Integrations, API & Tokens
facebook-pagesocial-wallEl ID de la página, el nombre que se mostrará y un token de acceso de página con pages_read_engagement y pages_show_list del Explorador de la API Graph
instagram-businesssocial-wallEl ID de la cuenta de Instagram, el nombre que se mostrará y el token de acceso de la página de Facebook vinculada, con instagram_basic, pages_show_list y pages_read_engagement
google-reviewsreviewsEl ID del lugar y una clave de API de Google Cloud con la API Places (New) habilitada
google-travel-timestravel-timesUna dirección de partida, de uno a ocho destinos, en coche o en transporte público, y una clave de API de Google Cloud con la API Routes habilitada
greenhouse-jobsnow-hiringEl token del tablero de boards.greenhouse.io/TOKEN. Sin credencial: el tablero es público
lever-jobsnow-hiringEl nombre del sitio de jobs.lever.co/NAME y la región. Sin credencial
ashby-jobsnow-hiringEl nombre del tablero de jobs.ashbyhq.com/NAME. Sin credencial
github-actionsbuild-boardEl propietario, el repositorio y la rama, y un token de acceso personal de granularidad fina con acceso de solo lectura a las Actions de ese repositorio, de github.com/settings/personal-access-tokens

El panel, su agente y la API listan las opciones predefinidas, con la ayuda y el ejemplo de cada campo y los pasos para obtener cada credencial: GET /api/source-presets las devuelve todas y GET /api/source-presets/NAME una (el grupo Fuentes de datos de la referencia de la API). Cree una fuente a partir de una opción predefinida con preset, settings y secrets en lugar de kind y config. Guarde la credencial en el archivo de la solicitud, no en la línea de comandos, y sustituya BOT_TOKEN por el token:

{
  "name": "Announcements",
  "preset": "slack-channel",
  "target": {"card_id": "crd_CARD"},
  "settings": {"channel": "C0123456789", "channel_label": "#announcements"},
  "secrets": {"token": "BOT_TOKEN"}
}
curl -sS https://api.screenrig.ai/api/sources/test \
  -H "Authorization: Bearer $SCREENRIG_TOKEN" -H "Content-Type: application/json" -d @source.json

POST /api/sources/test imprime el documento que mostraría la tarjeta, si la tarjeta lo acepta y lo que observó, y no escribe nada. POST /api/sources guarda el mismo archivo, y PATCH /api/sources/ID con nuevos settings vuelve a crear la fuente a partir de la opción predefinida.

  • Su cuenta, sus condiciones. Una opción predefinida lee su propia cuenta con una credencial que usted crea, bajo las condiciones del servicio, y la tarjeta muestra lo que esas condiciones exigen: las reseñas de Google aparecen con la marca de Google y el nombre de cada autor.
  • Horario y caducidad. Cada opción predefinida fija su propio intervalo y su propio periodo de caducidad, por ejemplo un minuto y 15 minutos para Slack, o 6 horas y 24 horas para las reseñas de Google. Una fuente puede elegir otro intervalo, nunca uno más corto que el mínimo de su opción predefinida. Cuando una sincronización falla, la tarjeta conserva sus últimos datos hasta que pasa el periodo de caducidad y luego queda en blanco.
  • Facturación en el servicio. Cada actualización de las reseñas de Google es una solicitud Place Details en su proyecto de Google Cloud, y cada actualización de los tiempos de trayecto de Google es un elemento de ruta por cada destino: cuatro destinos cada 15 minutos son unos 11 500 elementos al mes.
  • Slack necesita una aplicación interna. No la distribuya: Slack limita a una solicitud por minuto una aplicación distribuida fuera de su Marketplace. La aplicación solo lee los canales a los que se añadió, y la tarjeta muestra los mensajes propios de las personas, no las incorporaciones, las publicaciones de bots ni los avisos del canal.

Crear una fuente

Cree primero la tarjeta, luego haga una vista previa y después guarde.

screenrig card create --type events --file events.json
screenrig source test --kind ics --card crd_CARD \
  --set 'calendars=[{"key":"lobby"}]' --set time_zone=America/Vancouver \
  --secret calendar_url.lobby=@lobby.url
screenrig source create --kind ics --card crd_CARD --name "Lobby calendar" \
  --set 'calendars=[{"key":"lobby"}]' --set time_zone=America/Vancouver \
  --secret calendar_url.lobby=@lobby.url
screenrig source show src_SOURCE

source test obtiene la dirección, la adapta a la tarjeta y comprueba el resultado con la tarjeta, sin guardar ni escribir nada. Muestra el documento, si la tarjeta lo acepta y lo que ha observado por el camino. Indique la dirección en un archivo (@FILE) o por la entrada estándar (-) para que no quede en el historial de su shell.

ComandoQué hace
source list, source show IDLas fuentes del proyecto, con su estado, ajustes y secretos enmascarados
source createGuarda una fuente y la ejecuta en unos segundos
source update IDCambia el nombre, la tarjeta, los ajustes (--set KEY=VALUE o --settings JSON), los secretos (--secret, --unset-secret), la programación (--refresh 10m) o --enable y --disable
source testHace una vista previa de una fuente, guardada o no, sin escribir nada
source refresh IDSincroniza al instante y muestra el resultado
source delete IDElimina la fuente; la tarjeta conserva su último documento

--settings recibe toda la configuración de la fuente como JSON, @FILE o -. La opción --config de la CLI indica su archivo de credenciales y no tiene nada que ver con las fuentes.

Cuando una fuente no puede actualizarse

Una fuente escribe cada documento con un plazo: su periodo de caducidad después de la escritura, que se renueva mientras la fuente sigue actualizándose. Mientras la fuente funciona, el plazo se va desplazando. Cuando falla, la tarjeta sigue mostrando los últimos datos buenos hasta el plazo, y entonces los datos caducan en todas las pantallas, también las que están sin conexión.

  • Define el periodo con --stale-after 30m en source create o source update, de 1 minuto a 7 días y como mínimo el doble del intervalo de actualización. --stale-after none conserva los últimos datos buenos hasta la próxima actualización. Una fuente de reservas de recinto (momentus) los conserva 30 minutos salvo que indiques otra cosa; los demás tipos los conservan hasta la próxima actualización.
  • Recurrir a tu propio contenido. Una página cuya condición visibility.data nombra el documento (para una sala, momentus-room-{screen.field.room}) se muestra mientras los datos están y cede el paso a la página siguiente cuando caducan. Deja después una página normal, con tu propia marca, como respaldo.
  • Consultarlo. source show muestra stale_after, data_expires_at y, cuando los datos han caducado mientras la fuente sigue fallando, stale: true. La página Fuentes de datos del panel muestra lo mismo: una fuente con fallos aparece como Con fallos, y una cuyos datos han caducado como Caducada.
  • Recibir avisos. Una notificación sobre las condiciones source_failed (falló una actualización) y source_stale (los datos caducaron) envía un correo o publica en Slack, Teams, PagerDuty o Telegram, como para una pantalla: screenrig notifications create --email owner --conditions source_failed,source_stale --hold 5. Un aviso nombra la fuente y enlaza su página, se cierra cuando una actualización funciona, y --tag nunca coincide con una fuente.

Estado y errores

Cada fuente muestra su estado: cuándo se ejecutó por última vez, cuándo tuvo éxito y cuándo cambió la tarjeta por última vez, cuándo se ejecutará de nuevo, cuántas sincronizaciones seguidas han fallado y el último error.

EstadoSignificado
pendingLa fuente aún no se ha ejecutado
okLa última obtención se completó y la tarjeta aceptó el documento
errorLa última sincronización falló; la tarjeta conserva su último documento válido
disabledLa programación está detenida

ok no juzga si los datos de origen son correctos o actuales, ni si una pantalla muestra la tarjeta.

Código de errorQué ocurrió
unreachablescreenRIG no pudo acceder a la dirección. Use una dirección http(s) pública en el puerto 80 o 443
timeoutLa dirección no respondió en 20 segundos
too_largeLa respuesta supera los 5 MB
http_statusLa dirección respondió con un error HTTP. Un 404 significa que la dirección ya no es correcta
auth_failedLa dirección rechazó las credenciales; el siguiente intento espera una hora
not_sharedUna hoja de Google no está compartida: Google respondió con una página de inicio de sesión, un 401 o un 403. Compártala con Cualquier persona que tenga el enlace, o publíquela en la web
parse_failedLa respuesta no es un calendario. Una página de inicio de sesión se lee como una página web, no como un calendario: use la dirección ICS del calendario
rate_limitedEl servicio está limitando las solicitudes; la siguiente sincronización espera
mapping_failedEl mapeo no pudo leer la respuesta en el lugar que nombra el mensaje, por ejemplo un rows_path que no encuentra ninguna matriz
card_data_invalidLa tarjeta rechazó el documento en la ruta que indica el mensaje
target_missingSe eliminó la tarjeta. Apunte la fuente a otra tarjeta
payment_requiredEl almacenamiento del proyecto está lleno, así que no se escribió el documento
config_invalidUn ajuste o un secreto almacenado ya no se puede usar. Introdúzcalo de nuevo
internalLa sincronización falló dentro de screenRIG y se reintenta

Crear o cambiar una fuente puede responder source_target_taken (la tarjeta ya tiene una fuente), source_target_unsupported (el tipo no puede rellenar esa tarjeta; la respuesta indica las tarjetas posibles), source_secret_required (falta un secreto, o se introdujo para otra dirección y debe introducirse de nuevo), source_limit_reached y source_busy (hay una sincronización en curso; reintente tras el intervalo). source test responde source_test_failed con el motivo cuando no se puede leer la dirección.

Calendarios

Una fuente de calendario lee direcciones iCalendar (ICS): la dirección secreta que Google Calendar, Outlook, iCloud y Nextcloud dan para un calendario, y cualquier enlace webcal://. Rellena una tarjeta events con una agenda de todos los calendarios, o una tarjeta study-rooms con una sala por calendario.

ConfiguraciónSignificado
calendarsDe 1 a 60 calendarios, cada uno con {key, name, floor, capacity}. La clave admite letras minúsculas, dígitos, - y _; el nombre, la planta y la capacidad describen una sala
daysCuántos días mostrar a partir de hoy, de 1 a 31 (7 por defecto)
time_zoneUna zona horaria IANA como America/Vancouver, que se usa para el límite del día y para las horas que un feed deja sin zona
private_titleEl título que se muestra para los eventos privados (por defecto Private)
include, excludePalabras clave que conservan u ocultan eventos según el título: vea Conservar u ocultar eventos por palabra clave

Cada calendario tiene un secreto, calendar_url.KEY, que contiene su dirección.

  • Los eventos recurrentes (reglas diarias, semanales, mensuales y anuales, excepciones, repeticiones movidas y canceladas) y las zonas horarias se expanden en el servidor, de modo que un evento conserva su hora con el cambio de horario y una tarjeta nunca repite las reglas del calendario. Se reconocen los nombres de zona horaria de Windows que usa Outlook.
  • Los eventos privados (CLASS:PRIVATE o CONFIDENTIAL) muestran solo sus horarios, con el título privado y sin organizador ni lugar. La regla cubre los marcados como privados y no juzga lo que revelan los demás eventos.
  • El tiempo libre (eventos marcados como transparentes o libres) se conserva en una agenda y se omite en un panel de salas, donde solo cuenta el tiempo ocupado. Los eventos cancelados se descartan y los provisionales cuentan como ocupados.
  • Los eventos de todo el día se muestran como fechas; un evento sin final no muestra final.
  • Advertencias. Cuando un feed no indica zona horaria, usa una que screenRIG no reconoce, o contiene más de 500 repeticiones de un evento o más de 5000 eventos, la sincronización se completa y el estado muestra una advertencia.
  • Varios calendarios se sincronizan juntos: si uno falla, falla toda la sincronización y el error nombra el calendario, de modo que un panel nunca muestra un conjunto parcial.

Conservar u ocultar eventos por palabra clave

Las fuentes ics, microsoft365, google_calendar y momentus aceptan dos ajustes opcionales, include y exclude, cada uno una lista de 1 a 20 palabras clave de 1 a 100 caracteres. Un evento se conserva cuando su título contiene al menos una palabra clave de include (si include está definido) y ninguna de las de exclude. Una palabra clave coincide dentro de un título sin distinguir mayúsculas ni acentos, así que café y CAFE coinciden entre sí.

  • Solo el título. La ubicación, el organizador y la descripción nunca se comparan. Un evento sin título se compara como Untitled event.
  • Los eventos privados se comparan por su propio título y siguen mostrando el título privado. El título privado en sí nunca se compara, así que exclude: ["private"] no elimina nada.
  • Todo lo que escribe la fuente lee los calendarios filtrados: la tarjeta y cada documento de sala por clave. Un filtro elimina solo eventos, nunca un calendario ni una sala, y los eventos libres cuentan como los demás.
  • El estado cuenta lo que se dejó fuera como filtered, incluido el 0. Cuando la ventana contiene eventos y las palabras clave no dejan ninguno, la sincronización se completa con una advertencia que pide revisarlas, y la tarjeta muestra una lista vacía. source test muestra lo mismo.
screenrig source create --kind ics --card crd_EVENTS --name "Public events" \
  --set 'calendars=[{"key":"main"}]' --set 'include=["open house","tour"]' --set 'exclude=["cancelled"]' \
  --secret calendar_url.main=@calendar.url

Hojas, CSV y JSON

Una fuente sheets, csv o json lee una tabla o un documento JSON y crea con ello los datos de una tarjeta mediante un mapeo, de modo que sirve para cualquier tarjeta: un menú a partir de una lista de precios, un panel de indicadores a partir de una hoja de métricas, una tabla a partir de una hoja de clasificación. El mapeo es una plantilla JSON en los ajustes de la fuente. Las reglas propias de la tarjeta deciden si se acepta lo que genera el mapeo, y source test muestra el documento y si la tarjeta lo acepta, así que edita el mapeo hasta que lo haga. Estas fuentes se sincronizan cada 5 minutos, y cada minuto como mínimo.

screenrig source test --kind sheets --card crd_MENU --settings @menu-sheet.json
screenrig source create --kind sheets --card crd_MENU --name "Cafe menu" --settings @menu-sheet.json
screenrig source create --kind json --card crd_KPI --name "Daily numbers" \
  --settings @kpi-api.json --secret token=@api.token
ConfiguraciónTiposSignificado
urltodoEl enlace de la hoja, o la dirección del archivo CSV o del documento JSON: una dirección http(s) pública en el puerto 80 o 443
mappingtodoLa plantilla que genera los datos de la tarjeta. Obligatorio
key_templatetodoClave opcional con marcadores {screen.field.NAME}; la transformación devuelve un arreglo documents de {fields,document}
sheetsheetsEl nombre de una pestaña, para leer una pestaña distinta de la que nombra el enlace
header_rowsheets, csvtrue (predeterminado) cuando la primera fila contiene los nombres de las columnas; false cuando todas las filas son datos, con los nombres Column 1, Column 2
key_columnsheets, csvLa columna cuyas celdas nombran las filas, para que el mapeo pueda buscar una fila por su nombre
delimitercsv,, ; o tab. De forma predeterminada, el que más usa la primera línea
authcsv, jsonnone (predeterminado), bearer, header, basic, oauth2 o token_request: vea Solicitudes, inicio de sesión y páginas
header_namecsv, jsonCon auth header, el encabezado que lleva el token, por ejemplo X-API-Key
rows_pathjsonDónde está la matriz de filas en la respuesta, por ejemplo data.items
key_fieldjsonEl miembro de cada fila que la nombra. Establézcalo junto con rows_path

Compartir una hoja de Google

Una fuente sheets lee la hoja mediante la exportación CSV de Google. No necesita iniciar sesión ni acceder a su cuenta de Google, y la hoja debe poder leerla cualquiera que tenga su enlace.

  1. En Google Sheets elija Compartir, establezca el Acceso general en Cualquier persona que tenga el enlace y el rol en Lector. Para publicar en su lugar, elija Archivo, Compartir, Publicar en la Web.
  2. Copie el enlace de la barra de direcciones del navegador en url. Un enlace a una pestaña conserva esa pestaña. Para leer otra pestaña por su nombre, establezca sheet; un enlace publicado no admite sheet.
  3. Haga una vista previa con source test. La fila de encabezado de la hoja se convierte en las claves de columna que lee el mapeo.

Una hoja que no está compartida responde con una página de inicio de sesión, un 401 o un 403. El estado es entonces not_shared, y el mensaje nombra el ajuste que hay que cambiar: Compartir, Acceso general, Cualquier persona que tenga el enlace, Lector, o Archivo, Compartir, Publicar en la Web. Pida al propietario de la hoja que la comparta. not_shared solo juzga esas tres respuestas. No compara las columnas con su mapeo.

Direcciones con token

Un archivo CSV o un documento JSON en una dirección pública no necesita token. Para uno protegido, establezca auth en bearer (screenRIG envía Authorization: Bearer TOKEN) o en header con header_name (envía HEADER_NAME: TOKEN), y entregue el token como el secreto token:

screenrig source create --kind csv --card crd_TABLE --name "Standings" \
  --settings @standings.json --secret token=@api.token

El token se almacena cifrado y solo se muestra como una pista: sus últimos cuatro caracteres si tiene 12 o más, y en caso contrario …. Se envía únicamente al origen de la propia dirección: el esquema, el host y el puerto. Una redirección a otro sitio no se sigue con el token, y apuntar la fuente a otro origen sin volver a introducir el token se rechaza con source_secret_required. Cambiar solo la ruta o la consulta conserva el token. Para basic, OAuth 2 y las solicitudes de token, y para una solicitud con cuerpo, encabezados o páginas, vea Solicitudes, inicio de sesión y páginas. header_name no puede ser Host, Cookie, Content-Length, Connection, Transfer-Encoding, Upgrade ni un encabezado Proxy-, ni un encabezado que screenRIG establece por sí mismo.

Solicitudes, inicio de sesión y páginas

Una fuente csv o json envía un GET a su url. Estos ajustes envían un cuerpo o encabezados adicionales, inician sesión, leen más de una página y ponen la fecha de hoy en una solicitud. Cada solicitud tiene 20 segundos y una ejecución completa tiene 60.

ConfiguraciónSignificado
methodGET (predeterminado) o POST. Un body o un graphql lo convierte en POST
bodyUn objeto o matriz JSON, o texto enviado tal cual. Como máximo 16 KiB una vez rellenados sus marcadores
body_formatjson (predeterminado) o form. Un cuerpo de formulario es un objeto de campos de texto, número y booleanos, o un texto como a=1&b={{today}}; los valores se codifican para URL
graphql{query, variables}, enviado como un POST JSON. Una respuesta con errores y sin datos es parse_failed; los errores junto a datos son una advertencia
headersHasta 8 encabezados adicionales cuyos valores no son secretos. Authorization se rechaza, y también cualquier encabezado que header_name rechace: use auth o un secreto de encabezado
time_zoneUna zona IANA para los marcadores de fecha. Sin ella se leen en UTC y el estado muestra una advertencia
paginationLee más de una página, como se describe abajo
formatSolo el tipo json: auto (predeterminado), json, csv o xml

Un secreto llamado header.NAME se envía como el encabezado NAME, así que una API con dos encabezados de credenciales usa header.X-Client-Id y header.X-Api-Key. Un secreto llamado query.NAME se añade a la dirección como NAME=value. Cada tipo admite hasta 8, ambos se almacenan sellados y nunca se muestran, y la vista previa muestra un secreto de consulta como NAME=***.

AutenticaciónAjustes y secretos
noneEl valor predeterminado
bearerEl secreto token, enviado como Authorization: Bearer
headerheader_name y el secreto token
basicusername (sin dos puntos) y el secreto password, enviados como Authorization: Basic
oauth2Credenciales de cliente: token_url, client_id, scope y audience opcionales, token_auth (body, el predeterminado, o basic) y el secreto client_secret. screenRIG pide un token a token_url y lo envía como Authorization: Bearer
token_requestUn inicio de sesión que devuelve un token: token_url, token_body (un objeto cuyas cadenas pueden leer los secretos almacenados como {{secrets.NAME}}), token_body_format (json o form), token_path (dónde está el token en la respuesta, como token.accessToken) y, opcionalmente, token_expires_path, token_ttl (de 60 a 86 400 segundos, 3600 por defecto) y header_name (el encabezado que lleva el token tal cual; sin él, Authorization: Bearer)

screenRIG guarda un token en memoria, nunca en almacenamiento, y lo renueva un minuto antes de que caduque. Cuando la dirección rechaza un token con un 401, pide uno nuevo una vez. Un rechazo en la dirección del token (HTTP 400, 401 o 403) es auth_failed, y nada de lo que dice la dirección del token se cita.

{
  "url": "https://api.example.com/v2/orders/summary",
  "method": "POST",
  "body": {"from": "{{today-7d}}", "to": "{{today}}"},
  "time_zone": "America/Vancouver",
  "auth": "oauth2",
  "token_url": "https://login.example.com/oauth/token",
  "client_id": "screen-signs",
  "scope": "orders.read",
  "mapping": {"body": "{{body.total}} orders this week"}
}
screenrig source create --kind json --card crd_BOARD --name "Orders" \
  --settings @orders.json --secret client_secret=@client.secret

Los marcadores de fecha rellenan la dirección, el cuerpo y headers: {{now}} (2026-11-01T17:30:00Z), {{today}} (2026-11-01) y {{unix}} (segundos), cada uno con un desfase opcional como {{now-90m}}, {{now+7d}} o {{today-1d}} (unidades m, h y d; {{today}} solo admite días). Se leen en time_zone, o en UTC. Cada ejecución los rellena con el reloj, así que la ejecución de mañana pide la fecha de mañana.

Páginas. pagination es {type, max_pages}, con max_pages de 1 a 20 (5 por defecto). Una fuente json define rows_path para que las filas de las páginas se unan en una sola matriz, y una fuente csv une las filas de la tabla.

TipoCómo se encuentra la página siguiente
link_headerLa dirección rel="next" del encabezado Link
next_urlpath, el lugar de la respuesta que contiene la dirección siguiente (solo json)
cursorparam y path: el cursor va en la dirección como param, o en el cuerpo con "in": "body", y la lectura termina cuando el cursor falta o está vacío (solo json)
pageparam, start (1 por defecto) y size_param con size: el número de página va en la dirección o, con "in": "body", en el cuerpo, y la lectura termina en una página sin filas o con menos de size

La dirección siguiente debe permanecer en el origen de la propia fuente, así que un secreto nunca sale de él. Una página repetida, o más páginas que max_pages, termina la lectura con una advertencia, y todas las páginas juntas pesan como máximo 5 MB.

XML y RSS. Una respuesta que empieza por <, o format xml, se lee como XML. body contiene el elemento raíz: los atributos son @name, el texto de un elemento que tiene hijos es #text, los elementos repetidos son listas y los nombres conservan su prefijo (item["media:thumbnail"]["@url"]). Un feed RSS o Atom también recibe body.items, cada entrada con title, link, published, summary (texto sin formato, hasta 1000 caracteres) y image, cuando la entrada los tiene. Un DOCTYPE nunca se sigue y no se obtiene nada de él.

source test muestra en request la primera solicitud tal como salió: los marcadores rellenados, los secretos de consulta como NAME=***, los nombres de los encabezados distintos de Accept y Content-Type sin sus valores, el primer KiB del cuerpo y el número de páginas leídas.

Qué lee una fuente

  • CSV y hojas. CSV común: se leen la marca de orden de bytes, los campos entre comillas, las comillas dobles, los saltos de línea dentro de comillas y los finales de línea CRLF. Las celdas se recortan y las filas sin contenido se descartan. Un encabezado se convierte en una clave en minúsculas, sin acentos y con cada serie de otros caracteres convertida en un solo _, de modo que Sold out pasa a ser sold_out; un encabezado vacío se convierte en column_3 (su posición) y uno repetido en _2, _3. Un archivo que no es texto UTF-8, como un archivo de hoja de cálculo, es parse_failed: guárdelo como CSV en UTF-8.
  • JSON. Los números conservan sus dígitos. Con rows_path la fuente busca allí la matriz de filas; una ruta que no encuentra ninguna matriz es mapping_failed en rows_path, y una matriz vacía es una tabla sin filas.
  • Límites. Una tabla se lee hasta 10 000 filas y 200 columnas. Más allá, la sincronización tiene éxito y el estado muestra una advertencia que cuenta las filas o columnas omitidas.
  • Una página web en la dirección, en lugar de los datos, es parse_failed.

El mapeo

El mapeo es una plantilla JSON. Una cadena con marcadores como {{item.price}} es texto, y una cadena que es exactamente un solo marcador conserva el tipo del valor. Una ruta que no contiene nada queda vacía en un texto y omite un valor suelto, de modo que la tarjeta nombra entonces lo que le falta. Desde dónde puede empezar una ruta depende del tipo:

TipoPunto de partidaContiene
sheets, csvrowsCada fila de datos, un objeto por clave de columna
sheets, csvcolumns[{key, name}] en el orden de la hoja
sheets, csvby_keyCon key_column: las filas según el texto recortado de la celda de esa columna
jsonbodyToda la respuesta
jsonby_keyCon rows_path y key_field: las filas según el texto, número o booleano recortado de ese miembro

Prefiera by_key a una posición como rows[2]: una fila encontrada por su clave sigue encontrándose cuando alguien ordena la hoja o inserta una fila encima. Escriba una clave con espacios así: by_key["Net sales"].value. Si dos filas comparten una clave, gana la primera y el estado muestra una advertencia que cuenta las repeticiones. Ninguna advertencia cita una celda.

Las celdas son texto, así que conviértalas: {"$number": "{{item.price}}"} lee 1,234.50, $4.50, 3,50 € y 12% (añada "$decimal": "," para una coma decimal), y {"$bool": "{{item.sold_out}}"} lee true, yes, y, 1 y x. $each recorre una lista con item y index disponibles, $group con $by agrupa las filas en el orden de primera aparición con group.key y group.items disponibles, y $text con $max recorta el texto. La plantilla se comprueba al guardar la fuente.

Un menú a partir de una hoja con las columnas Section, Item, Price y Sold out agrupa las filas por sección:

{
  "url": "https://docs.google.com/spreadsheets/d/SHEET_ID/edit#gid=0",
  "mapping": {
    "sections": {
      "$group": "{{rows}}", "$by": "{{item.section}}",
      "$template": {
        "name": "{{group.key}}",
        "items": {
          "$each": "{{group.items}}",
          "$template": {
            "name": "{{item.item}}",
            "price": { "$number": "{{item.price}}" },
            "soldOut": { "$bool": "{{item.sold_out}}" }
          }
        }
      }
    }
  }
}

Un panel de indicadores a partir de una hoja con las columnas Metric, Value, Unit y Change busca cada métrica por su nombre, así que ordenar la hoja no mueve nada:

{
  "url": "https://docs.google.com/spreadsheets/d/SHEET_ID/edit#gid=0",
  "key_column": "Metric",
  "mapping": {
    "tiles": [
      { "label": "Revenue", "value": { "$number": "{{by_key.Revenue.value}}" },
        "unit": "{{by_key.Revenue.unit}}", "delta": { "$number": "{{by_key.Revenue.change}}" } },
      { "label": "Orders", "value": { "$number": "{{by_key.Orders.value}}" },
        "delta": { "$number": "{{by_key.Orders.change}}" } }
    ]
  }
}

Una tarjeta de tabla muestra todas las filas:

{
  "mapping": {
    "rows": {
      "$each": "{{rows}}", "$limit": 50,
      "$template": {
        "team": "{{item.team}}",
        "points": { "$number": "{{item.points}}" },
        "played": { "$number": "{{item.played}}" }
      }
    }
  }
}

Para ver qué lee una fuente, pruebe con el mapeo {"columns": "{{columns}}", "rows": "{{rows}}"}. La tarjeta rechaza ese documento, y la vista previa lo muestra igualmente, con cada clave y cada fila.

ok en una de estas fuentes significa que la dirección respondió y la tarjeta aceptó el documento generado por el mapeo. No indica que la hoja o la respuesta contenga las filas que espera su mapeo: una columna que desaparece deja fuera su miembro, y la tarjeta rechaza un documento al que le falta uno obligatorio.

Valores calculados

Un mapeo también puede calcular verdadero o falso y números en el servidor, así que una condición de página que solo comprueba igualdad, como visibility.data con equals: true, puede seguir un número. Los mapeos de webhooks de tarjeta y de activadores de superposición tienen las mismas funciones.

FunciónDevuelveEscritura
$gt, $gte, $lt, $lteVerdadero o falso, leyendo ambos valores como números{"$lt": ["{{body.stock}}", 5]}
$betweenVerdadero cuando el valor está dentro de ambos límites, inclusive{"$between": ["{{body.temp}}", 18, 24]}
$eq, $neNúmeros por valor (5, "5" y "5.00" coinciden), todo lo demás como texto exacto, con mayúsculas y minúsculas{"$eq": ["{{body.status}}", "open"]}
$containsUn texto contiene otro texto, sin distinguir mayúsculas, o una lista contiene un valor{"$contains": ["{{body.title}}", "closed"]}
$inEl valor es uno de una lista{"$in": ["{{body.tier}}", ["gold", "silver"]]}
$and, $or, $notCombinan condiciones{"$and": [{"$gt": ["{{body.a}}", 1]}, {"$not": {"$eq": ["{{body.s}}", "x"]}}]}
$sum, $avg, $min, $maxUn número a partir de una lista{"$sum": "{{rows}}", "$of": "{{item.amount}}"}
$countCuántos elementos hay, o cuántos cumplen $where{"$count": "{{body.queue}}", "$where": {"$gt": ["{{item.wait}}", 5]}}
  • Una comparación que no puede hacerse es falsa, con una advertencia que nombra el lugar y nunca el valor, así que un valor ausente, vacío o no numérico nunca activa una página. $bool es el único lector que toma un valor ausente como falso.
  • Listas. $of lee un marcador de cada elemento, con item y index disponibles, y $where conserva los elementos para los que se cumple una condición. Un elemento vacío o que no es un número se omite, y una advertencia los cuenta. Si no hay nada que sumar, $sum y $count valen 0, y $avg, $min y $max omiten su miembro.
  • Números. Una coma decimal (3,50, 1.234,50) necesita "$decimal": ",". $avg redondea a 2 decimales, y "$round" fija de 0 a 6. $sum, $min y $max son exactos.

Un indicador de poco stock a partir de una respuesta JSON. Una página cuyo visibility.data nombra el documento con path /low_stock y equals true se muestra solo mientras hay poco stock:

{
  "sku": "{{body.sku}}",
  "stock": {"$number": "{{body.stock}}"},
  "low_stock": {"$lt": ["{{body.stock}}", 5]}
}

A partir de una hoja, cualquier producto por debajo de su propio nivel de reposición:

{"low_stock": {"$gt": [{"$count": "{{rows}}", "$where": {"$lt": ["{{item.stock}}", "{{item.reorder_at}}"]}}, 0]}}

Documentos por clave

Las fuentes JSON, CSV y Sheets aceptan key_template, por ejemplo room-{screen.field.room}. Transforma el feed en un arreglo documents de objetos {"fields":{"room":"A101"},"document":{"body":"Room A101"}}. Un webhook de tarjeta usa --key-template con la misma salida, o recibe el objeto contenedor directamente sin transformación.

{
  "key_template": "room-{screen.field.room}",
  "mapping": {
    "documents": {
      "$each": "{{rows}}",
      "$template": {
        "fields": {"room": "{{item.room}}"},
        "document": {"body": "{{item.title}}"}
      }
    }
  }
}

Coloca la instancia crd_ correspondiente con data: "room-{screen.field.room}"; cada pantalla lee solo la clave formada con su propio campo room. Todas las salidas usan el esquema de tarjeta de la instancia. Usa instancias, transformaciones y prefijos separados para distintos tipos de tarjeta que lean el mismo feed.

Previsualiza con source test. Los documentos no válidos, los campos ausentes y las claves duplicadas rechazan el lote antes de escribir. Un fallo de almacenamiento puede dejar una parte escrita: reintenta el mismo cuerpo del webhook con el mismo Idempotency-Key, o actualiza la fuente. Las claves omitidas conservan sus datos y su vencimiento anteriores; los webhooks no establecen vencimiento. Borra la plantilla de un webhook con --clear-key-template.

Consejos para archivos y hojas

  • OneDrive y SharePoint. Un enlace para compartir abre una página de vista previa, que una fuente lee como una página web (parse_failed). Añada ?download=1 al enlace de un archivo CSV, o &download=1 si ya tiene un ?, para que el enlace devuelva el archivo mismo. El enlace debe funcionar sin iniciar sesión.
  • Busque filas por nombre. Establezca key_column en la columna que da nombre a cada fila, por su encabezado sin distinguir mayúsculas, y lea una fila como by_key.NAME.column: ordenar la hoja o insertar una fila no cambia nada. Un nombre que no coincide con ninguna columna da mapping_failed en key_column, y una clave repetida conserva la primera fila con una advertencia que cuenta las repeticiones.

Salas de reuniones

Una fuente de salas lee calendarios de salas de reuniones y alimenta una tarjeta room, el cartel de puerta de una sala, o una tarjeta room-board, todas las salas de un edificio. También puede alimentar una agenda events o una tarjeta study-rooms, y una dirección de calendario (ICS) alimenta las mismas tarjetas.

Las tarjetas calculan en la pantalla, cada segundo, si una sala está libre, en uso o qué viene después, a partir de las reservas que tienen: un cartel sigue siendo correcto aunque su pantalla esté sin conexión. Una fuente de salas solo lee: una tarjeta muestra reservas y no admite entradas, así que desde una pantalla no se reserva, termina ni registra nada.

ConfiguraciónSignificado
roomsDe 1 a 60 salas, cada una con una key (letras minúsculas, dígitos, - y _), la dirección de la sala (email en Microsoft 365, calendar_id en Google) y, opcionalmente, name, floor y capacity. Una tarjeta room admite exactamente una sala
daysCuántos días mostrar a partir de hoy, de 1 a 31 (7 por defecto)
time_zoneUna zona IANA como America/Los_Angeles, que se usa para el límite del día y para los eventos de todo el día. Sin ella, el día empieza a medianoche UTC y el estado muestra una advertencia
private_titleEl título que se muestra para los eventos privados (por defecto Private)

Buzones de sala de Microsoft 365

El tipo microsoft365 lee los buzones de sala de Exchange Online con tu propia aplicación de Microsoft Entra, de modo que la aplicación, su secreto y lo que puede leer siguen siendo tuyos. Dos ajustes más: tenant, el ID del directorio o un dominio verificado como contoso.com (common, organizations y consumers se rechazan), y client_id, el ID de aplicación (cliente) de la aplicación. El secreto client_secret es el valor del secreto de cliente de la aplicación.

  1. En el centro de administración de Microsoft Entra, registra una aplicación de un solo inquilino sin URI de redirección, agrega un secreto de cliente (copia su valor, no su ID de secreto), agrega el permiso de aplicación de Microsoft Graph Calendars.Read y concede el consentimiento del administrador.
  2. Limita la aplicación a los buzones de sala. Calendars.Read llega a todos los buzones de la organización hasta que una directiva de acceso de aplicaciones lo restringe. Pon las salas en un grupo de seguridad habilitado para correo y ejecuta los dos primeros comandos de abajo en Exchange Online PowerShell; Test-ApplicationAccessPolicy responde Granted para una sala del grupo. El RBAC de Exchange para aplicaciones limita una aplicación del mismo modo.
  3. Los buzones de sala sustituyen el asunto de una reunión por el nombre del organizador, salvo que se indique otra cosa. Para mostrar los títulos de las reuniones, ejecuta el tercer comando para cada sala.
  4. Haz una vista previa y luego guarda. source test inicia sesión, lee cada sala e informa de las salas y reservas leídas, un recuento por sala y, si la aplicación también tiene Place.Read.All, cuántas salas lista la organización.
# Exchange Online PowerShell
New-ApplicationAccessPolicy -AppId CLIENT_ID -PolicyScopeGroupId rooms@example.com -AccessRight RestrictAccess -Description "screenRIG room signs"
Test-ApplicationAccessPolicy -AppId CLIENT_ID -Identity boardroom@example.com
Set-CalendarProcessing -Identity boardroom@example.com -DeleteSubject $false -AddOrganizerToSubject $false

# screenRIG (source create takes the same options, plus --name)
screenrig source test --kind microsoft365 --card crd_ROOM \
  --set tenant=contoso.com --set client_id=CLIENT_ID --set time_zone=America/Los_Angeles \
  --set 'rooms=[{"key":"boardroom","email":"boardroom@example.com","name":"Boardroom","floor":"Level 4","capacity":12}]' \
  --secret client_secret=@secret.txt
  • Correspondencia. Las reuniones privadas y confidenciales muestran el título privado y ningún organizador. Las reuniones marcadas como libres o trabajando en otro lugar, y las que la sala rechazó, son tiempo libre; las provisionales cuentan como ocupadas; las canceladas se descartan. Los eventos de todo el día son fechas en la zona horaria de la sala.
  • Frecuencia. Una fuente de salas se sincroniza cada 2 minutos. El inicio de sesión dura alrededor de una hora y se reutiliza.
  • Errores. Se citan el código y el texto de error de Microsoft, por ejemplo AADSTS7000215 para un secreto de cliente incorrecto, y una sala que la aplicación no puede leer hace fallar la sincronización y se nombra.
  • Una conexión en su lugar. Establezca connection_id en una conexión de Microsoft del proyecto, en cualquiera de los dos modos, en lugar de tenant, client_id y client_secret. La fuente no guarda entonces ningún secreto e inicia sesión como lo hace la conexión.

Calendarios de recursos de Google Workspace

El tipo google_calendar lee calendarios de recursos y de salas con tu propia cuenta de servicio de Google. El secreto service_account_key es el archivo de clave tal como lo emite Google, y su pista es la dirección de la cuenta. El ajuste opcional subject nombra al usuario en cuyo nombre actúa la cuenta con la delegación en todo el dominio.

  1. En Google Cloud, habilita la API de Google Calendar, crea una cuenta de servicio y descarga una clave JSON para ella.
  2. Dale a la cuenta acceso a las salas. O bien comparte cada calendario de sala con la dirección de la cuenta y el permiso Ver todos los detalles del evento, o activa la delegación en todo el dominio para la cuenta en la consola de administración con el ámbito https://www.googleapis.com/auth/calendar.events.readonly y establece subject en un usuario que pueda ver las salas.
  3. El calendar_id de una sala es el ID de calendario del recurso en la consola de administración, en Edificios y recursos; termina en @resource.calendar.google.com. Haz una vista previa con source test y luego guarda.
screenrig source test --kind google_calendar --card crd_ROOM \
  --set time_zone=America/Los_Angeles \
  --set 'rooms=[{"key":"boardroom","calendar_id":"CALENDAR_ID","name":"Boardroom"}]' \
  --secret service_account_key=@key.json
  • Correspondencia. Los eventos privados y confidenciales muestran el título privado y ningún organizador. Los eventos marcados como transparentes, y los que la sala rechazó, son tiempo libre; los cancelados se descartan. Los eventos de todo el día conservan sus fechas.
  • Errores. Se cita el texto de error de Google. Un calendario que la cuenta no puede ver se nombra en el error, con la forma de compartirlo.
  • Una conexión en su lugar. Establezca connection_id en una conexión de Google del proyecto en lugar de service_account_key. Con la aplicación de screenRIG, la fuente lee los calendarios que puede ver la cuenta que la aprobó, así que esa cuenta debe ver cada calendario de sala que la fuente lista, y subject no está permitido.

Límites y privacidad

  • Una tarjeta room guarda las 200 reservas más próximas y una tarjeta room-board guarda 60 por sala. Si hay más, el estado muestra una advertencia con cuántas se dejaron fuera.
  • El documento de la tarjeta contiene solo lo que la tarjeta muestra, porque cada pantalla que muestra una tarjeta conserva su documento. Una tarjeta room recibe los organizadores solo con showOrganizer activado. Una tarjeta room-board nunca recibe organizadores y recibe el título privado en lugar de cada título de reunión, salvo que showTitles esté activado. Un cambio en cualquiera de los dos ajustes llega a la tarjeta en la siguiente sincronización. Una reunión privada lleva el título privado en ambos casos.
  • Los mensajes de estos tipos citan el texto de error que respondió Microsoft o Google, y nunca un secreto.

El sistema de reservas de un recinto

El tipo momentus lee las funciones (cada uso de una sala, con sus fechas y horas) y los espacios de una instalación de Momentus Enterprise, el sistema que antes se vendía como Ungerboeck y EBMS, con un usuario de API que tu administrador crea en Momentus (Main Menu, Api Users). Rellena una tarjeta events para un directorio de eventos, una tarjeta room-board para un vestíbulo o un pasillo, o una tarjeta room para una sala, y con un documento por sala, cada pantalla muestra las reservas de su propia sala. Una sala que cambia poco antes de un evento se muestra dentro del intervalo de actualización, un minuto por defecto.

ConfiguraciónSignificado
base_urlLa dirección https de tu servidor web de Momentus, por ejemplo https://momentus.example.com/prod; la API se lee desde {base_url}/api/v1/...
organizationEl código de organización de Momentus del que se leen los datos
api_userEl id del usuario de API
time_zoneLa zona IANA del recinto. Momentus guarda fechas y horas locales, así que defínela; sin ella las horas se leen como UTC y el estado muestra una advertencia
daysCuántos días leer desde hoy, de 1 a 31 (7 por defecto)
roomsLista opcional de {key, space, name?, floor?, capacity?, area?}; space es el código de espacio de Momentus. Sin ella, cada espacio que Momentus lista es una sala (clave: el código en minúsculas)
space_filter, function_filterExpresiones de búsqueda de Momentus opcionales que limitan los espacios o las funciones leídos
exclude_status_codesCódigos de estado de función que se excluyen, como los cancelados. Los códigos de estado son propios de tu recinto, así que no hay valor por defecto
include, excludePalabras clave que conservan u ocultan funciones según el título que muestra la fuente, como en los calendarios

Dos secretos: api_key, uno de los GUID de clave del usuario de API, y api_secret, su GUID secreto. Ambos se guardan cifrados, no se vuelven a mostrar y se introducen de nuevo si la fuente apunta a otro servidor, organización o usuario de API.

screenrig source test --kind momentus --card crd_EVENTS \
  --set base_url=https://momentus.example.com/prod --set organization=10 \
  --set api_user=venue-api --set time_zone=America/Los_Angeles \
  --set 'exclude_status_codes=["XX"]' \
  --secret api_key=@key.guid --secret api_secret=@secret.guid
screenrig source create --kind momentus --card crd_EVENTS --name "Venue events" \
  --settings @momentus.json --secret api_key=@key.guid --secret api_secret=@secret.guid

source test lee Momentus, da forma al documento y lo comprueba con la tarjeta, y muestra las salas y los eventos que leyó (functions, functions_other_spaces, events_KEY). No se guarda ni se escribe nada.

  • En qué se convierte una función. El Space de la función elige la sala, su Description es el título (title_field puede nombrar una de sus descripciones alternativas) y el nombre de la sala es la ubicación. Una función sin hora de inicio es un evento de todo el día. Una función de varias fechas con horas muestra esas horas en cada fecha; la que termina antes de empezar se prolonga durante la noche.
  • Títulos privados. Un room-board muestra el título privado salvo que show_titles esté activado, como en los demás tipos de sala.
  • Errores. El texto propio de Momentus nunca se cita, porque el servidor es tuyo: auth_failed significa que el usuario de API, la clave o el secreto es incorrecto o no puede leer funciones y espacios, config_invalid que un filtro no es una búsqueda válida de Momentus, y parse_failed que base_url llegó a una página web y no a la API.

Un cartel para cada sala

Define room_key_prefix (por ejemplo momentus-room-) y la fuente también escribe un documento room por cada sala bajo ese prefijo seguido de la clave de la sala. Asigna a la pantalla de cada sala su sala y coloca una tarjeta room cuya clave data sea el prefijo seguido del campo propio de la pantalla:

screenrig screen update scr_BALLROOM --field room=ballroom-a

Use el tipo de catálogo en línea room en esta primitiva de la lista de reproducción:

{"id":"door","primitive":"card","card":"room","layout":"full","content_fit":"fill","params":{"room":"{screen.field.room}"},"data":"momentus-room-{screen.field.room}"}

Las primitivas crd_ usan params y major de la propia instancia; omite esos campos. Su data opcional selecciona una clave de documento o una plantilla de clave por pantalla. --card crd_ID de la fuente sigue indicando su destino de sincronización. Para la página, establece visibility.data.key en momentus-room-{screen.field.room} y añade después una página alternativa sin condiciones.

El backend rellena el campo en el manifiesto de cada pantalla, así que una sola lista de reproducción muestra a cada pantalla su propia sala, y una pantalla sin campo de sala no muestra nada para esa tarjeta. area_key_prefix hace lo mismo para una pantalla de pasillo: el area de cada sala la agrupa, y una room-board de las salas de esa área se escribe bajo el prefijo seguido de la clave del área (area_card puede ser events). screen_field y area_field nombran los campos de pantalla que lee la comprobación de abajo (room y area por defecto).

Comprobar la asignación

screenrig source rooms src_ID (y la sección Asignación de salas de la página de la fuente en el panel) lista las salas que ofrece la fuente y que ninguna pantalla muestra, y las pantallas cuya sala falta o nombra una sala que Momentus no tiene:

screenrig source rooms src_VENUE

Una pantalla aparece en el informe cuando tiene un valor para el campo, o cuando su propia lista de reproducción coloca una tarjeta que lee la clave. Cada una es mapped, missing (la lista lee el campo y la pantalla no tiene ninguno) o unknown (el valor no nombra ninguna sala; el informe indica la sala que probablemente quiso decir, por ejemplo las mismas letras en otra capitalización). Corrige una pantalla con screenrig screen update scr_ID --field room=ROOM_KEY. El informe lee las salas de la última actualización correcta de la fuente y no consulta nada; una fuente que lista sus rooms las conoce antes de su primera actualización.

Seguridad

Una dirección que da acceso es una credencial. screenRIG la almacena cifrada, devuelve solo su esquema y su host, y la usa únicamente para ejecutar la sincronización. Las fuentes leen direcciones http(s) públicas en el puerto 80 o 443 y siguen como máximo tres redirecciones; una dirección que no se resuelve a un host público, una conexión rechazada y un fallo de negociación se leen igual, por lo que una fuente no sirve para explorar una red. Ningún mensaje cita una dirección ni la respuesta recibida. Las fuentes solo leen: nunca escriben en un calendario.