Personalizar la fuente para Content Cards
Una fuente de Content Cards es la secuencia de Content Cards en tus aplicaciones móviles o web. Este artículo cubre la configuración de cuándo se actualiza la fuente, el orden de las tarjetas, la gestión de múltiples fuentes y los mensajes de error de “fuente vacía”. Para obtener la lista completa de tipos de tarjetas de contenido, consulta Acerca de Content Cards.
Acerca del ciclo de vida de la sesión
Una sesión se refiere al período de tiempo durante el cual el SDK de Braze realiza el seguimiento de la actividad de los usuarios en tu aplicación después de su inicio. También puedes forzar una nueva sesión llamando alchangeUser()método .
De forma predeterminada, una sesión comienza cuando llamas por primera vez a braze.openSession(). La sesión permanecerá activa durante un máximo de30 minutos de inactividad (a menos que cambies el tiempo de espera predeterminado de la sesión o que el usuario cierre la aplicación).

Si has configurado la devolución de llamada del ciclo de](/docs/es/developer_guide/platform_integration_guides/android/initial_sdk_setup/android_sdk_integration/#step-4-tracking-user-sessions-in-android) vida de la actividad para Android, Braze llamará automáticamente aopenSession() ycloseSession() para cada actividad de tu aplicación.
De forma predeterminada, una sesión comienza cuandoopenSession()se llama por primera vez a . Si tu aplicación pasa a segundo plano y luego vuelve al primer plano, el SDK comprobará si han pasado más de 10 segundos desde que se inició la sesión (a menos que cambies el tiempo de espera predeterminado de la sesión). Si es así, comenzará una nueva sesión. Ten en cuenta que si el usuario cierra tu aplicación mientras está en segundo plano, es posible que los datos de la sesión no se envíen a Braze hasta que vuelva a abrir la aplicación.
Llamar nocloseSession() terminará inmediatamente la sesión. En su lugar, finalizará la sesión tras 10 segundos si el usuarioopenSession() no vuelve a llamar a iniciando otra actividad.
De forma predeterminada, una sesión comienza cuando llamas a Braze.init(configuration:). Esto ocurre cuando se desencadena laUIApplicationWillEnterForegroundNotificationnotificación, lo que significa que la aplicación ha pasado a primer plano.
Si tu aplicación pasa a segundo plano,UIApplicationDidEnterBackgroundNotification se desencadena. La aplicación no permanece en una sesión activa mientras está en segundo plano. Cuando tu aplicación vuelve al primer plano, el SDK compara el tiempo transcurrido desde el inicio de la sesión con el tiempo de espera de la sesión (a menos que cambies el tiempo de espera predeterminado). Si el tiempo transcurrido desde el inicio de la sesión supera el periodo de tiempo de espera, se inicia una nueva sesión.
Actualizar la fuente
Actualización automática
De forma predeterminada, la fuente de Content Cards se actualizará automáticamente cuando:
- Se inicie una nueva sesión
- La fuente predeterminada de Content Cards se cierre y se vuelva a abrir después de que hayan transcurrido más de 60 segundos desde la última actualización.

Para mostrar dinámicamente Content Cards actualizadas sin necesidad de actualizar manualmente, selecciona At first impression durante la creación de la tarjeta. Estas tarjetas se actualizarán cuando estén disponibles.
Entrega en tiempo real
Braze también envía actualizaciones de Content Cards al dispositivo en cuanto ocurren, a través de una conexión en vivo que el SDK mantiene durante la sesión. Los usuarios no necesitan iniciar una nueva sesión ni esperar una actualización para ver el cambio.
La entrega en tiempo real cubre las siguientes actualizaciones:
- Un usuario se vuelve elegible para una campaña de Content Cards durante una sesión.
- Un usuario avanza a un paso de Content Cards en un Canvas.
- Se elimina una tarjeta de la fuente de un usuario.
- Se envía una tarjeta a través de la API, como con el endpoint
/messages/send,/campaigns/trigger/sendo/canvas/trigger/send.
La entrega en tiempo real requiere las siguientes versiones mínimas del SDK:
En versiones anteriores del SDK, las tarjetas siguen llegando al inicio de sesión y durante la actualización.
Actualización manual
Para actualizar manualmente la fuente en un momento específico:
Solicita una actualización manual de Content Cards de Braze desde el SDK Web en cualquier momento llamando a requestContentCardsRefresh().
También puedes llamar a getCachedContentCards para obtener todas las tarjetas disponibles actualmente desde la última actualización de Content Cards.
1
2
3
4
5
import * as braze from "@braze/web-sdk";
function refresh() {
braze.requestContentCardsRefresh();
}
Para abrir los enlaces de Content Cards en una nueva pestaña del navegador en lugar de la misma pestaña, establece openCardsInNewTab: true en las opciones de inicialización del SDK Web. Para más información sobre las opciones de inicialización, consulta la guía del repositorio del SDK Web.
Solicita una actualización manual de Content Cards de Braze desde el SDK de Android en cualquier momento llamando a requestContentCardsRefresh.
1
Braze.getInstance(context).requestContentCardsRefresh();
1
Braze.getInstance(context).requestContentCardsRefresh()
Solicita una actualización manual de Content Cards de Braze desde el SDK de Swift en cualquier momento llamando al método requestRefresh en la clase Braze.ContentCards:
En Swift, Content Cards se pueden actualizar con un controlador de finalización opcional o con un retorno asíncrono utilizando las API nativas de concurrencia de Swift.
Controlador de finalización
1
2
3
AppDelegate.braze?.contentCards.requestRefresh { result in
// Implement completion handler
}
Async/Await
1
let contentCards = await AppDelegate.braze?.contentCards.requestRefresh()
1
2
3
[AppDelegate.braze.contentCards requestRefreshWithCompletion:^(NSArray<BRZContentCardRaw *> * contentCards, NSError * error) {
// Implement completion handler
}];
Sincronización completa vs. sincronización parcial
El SDK de Braze utiliza dos tipos de sincronización al obtener Content Cards del servidor:
- Sincronización completa: Descarga todas las Content Cards para las que un usuario es elegible. Las sincronizaciones completas se realizan automáticamente cada 7 días o cuando se llama a
changeUser(). - Sincronización parcial: Descarga solo las Content Cards nuevas desde la última solicitud. Si el usuario no es elegible para ninguna tarjeta nueva, la respuesta devuelve cero tarjetas. Las sincronizaciones parciales se realizan cada vez que se llama a
requestContentCardsRefresh()(a menos que hayan transcurrido 7 días desde la última sincronización completa, en cuyo caso se desencadena una sincronización completa).
Las sincronizaciones parciales reducen la carga del servidor y el consumo de batería del dispositivo. Las Content Cards que ya se han recibido se almacenan localmente en el SDK, por lo que los usuarios seguirán viendo sus tarjetas disponibles incluso cuando una sincronización parcial devuelva cero tarjetas nuevas.
Límite de velocidad
Braze utiliza un algoritmo de contenedor de tokens para aplicar los siguientes límites de velocidad:
- Hasta 5 llamadas de actualización por dispositivo, compartidas entre usuarios y llamadas a
openSession() - Después de alcanzar el límite, una nueva llamada estará disponible cada 180 segundos (3 minutos)
- El sistema mantendrá hasta cinco llamadas para que las utilices en cualquier momento
subscribeToContentCards()seguirá devolviendo tarjetas en caché incluso cuando se aplique el límite de velocidad

El SDK de Braze también aplica límites de velocidad para el rendimiento y la fiabilidad. Ten esto en cuenta al ejecutar pruebas automatizadas o realizar pruebas manuales de control de calidad. Consulta Límites de velocidad del SDK de Braze para más información.
Personalización del orden de visualización de las tarjetas
Puedes cambiar el orden en que se muestran tus Content Cards. Esto te permite ajustar la experiencia del usuario priorizando ciertos tipos de contenido, como promociones con tiempo limitado.
Personaliza el orden de visualización de las Content Cards en tu fuente usando el parámetro filterFunction de showContentCards():. Por ejemplo:
1
2
3
braze.showContentCards(null, (cards) => {
return sortBrazeCards(cards); // Where sortBrazeCards is your sorting function that returns the sorted card array
});
El ContentCardsFragment se apoya en un IContentCardsUpdateHandler para manejar cualquier ordenación o modificación de las Content Cards antes de que se muestren en la fuente. Se puede establecer un controlador de actualización personalizado mediante setContentCardUpdateHandler en tu ContentCardsFragment.
El siguiente es el IContentCardsUpdateHandler predeterminado y puede usarse como punto de partida para la personalización:
Mostrar ejemplo en Java
1
2
3
4
5
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class DefaultContentCardsUpdateHandler implements IContentCardsUpdateHandler {
// Interface that must be implemented and provided as a public CREATOR
// field that generates instances of your Parcelable class from a Parcel.
public static final Parcelable.Creator<DefaultContentCardsUpdateHandler> CREATOR = new Parcelable.Creator<DefaultContentCardsUpdateHandler>() {
public DefaultContentCardsUpdateHandler createFromParcel(Parcel in) {
return new DefaultContentCardsUpdateHandler();
}
public DefaultContentCardsUpdateHandler[] newArray(int size) {
return new DefaultContentCardsUpdateHandler[size];
}
};
@Override
public List<Card> handleCardUpdate(ContentCardsUpdatedEvent event) {
List<Card> sortedCards = event.getAllCards();
// Sort by pinned, then by the 'updated' timestamp descending
// Pinned before non-pinned
Collections.sort(sortedCards, new Comparator<Card>() {
@Override
public int compare(Card cardA, Card cardB) {
// A displays above B
if (cardA.getIsPinned() && !cardB.getIsPinned()) {
return -1;
}
// B displays above A
if (!cardA.getIsPinned() && cardB.getIsPinned()) {
return 1;
}
// At this point, both A & B are pinned or both A & B are non-pinned
// A displays above B since A is newer
if (cardA.getUpdated() > cardB.getUpdated()) {
return -1;
}
// B displays above A since A is newer
if (cardA.getUpdated() < cardB.getUpdated()) {
return 1;
}
// At this point, every sortable field matches so keep the natural ordering
return 0;
}
});
return sortedCards;
}
// Parcelable interface method
@Override
public int describeContents() {
return 0;
}
// Parcelable interface method
@Override
public void writeToParcel(Parcel dest, int flags) {
// No state is kept in this class so the parcel is left unmodified
}
}
Mostrar ejemplo en Kotlin
1
2
3
4
5
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class DefaultContentCardsUpdateHandler : IContentCardsUpdateHandler {
override fun handleCardUpdate(event: ContentCardsUpdatedEvent): List<Card> {
val sortedCards = event.allCards
// Sort by pinned, then by the 'updated' timestamp descending
// Pinned before non-pinned
sortedCards.sortWith(Comparator sort@{ cardA: Card, cardB: Card ->
// A displays above B
if (cardA.isPinned && !cardB.isPinned) {
return@sort -1
}
// B displays above A
if (!cardA.isPinned && cardB.isPinned) {
return@sort 1
}
// At this point, both A & B are pinned or both A & B are non-pinned
// A displays above B since A is newer
if (cardA.updated > cardB.updated) {
return@sort -1
}
// B displays above A since A is newer
if (cardA.updated < cardB.updated) {
return@sort 1
}
0
})
return sortedCards
}
// Parcelable interface method
override fun describeContents(): Int {
return 0
}
// Parcelable interface method
override fun writeToParcel(dest: Parcel, flags: Int) {
// No state is kept in this class so the parcel is left unmodified
}
companion object {
// Interface that must be implemented and provided as a public CREATOR
// field that generates instances of your Parcelable class from a Parcel.
val CREATOR: Parcelable.Creator<DefaultContentCardsUpdateHandler?> = object : Parcelable.Creator<DefaultContentCardsUpdateHandler?> {
override fun createFromParcel(`in`: Parcel): DefaultContentCardsUpdateHandler? {
return DefaultContentCardsUpdateHandler()
}
override fun newArray(size: Int): Array<DefaultContentCardsUpdateHandler?> {
return arrayOfNulls(size)
}
}
}
}

Para filtrar y ordenar Content Cards en Jetpack Compose, establece el parámetro cardUpdateHandler. Por ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ContentCardsList(
cardUpdateHandler = {
it.sortedWith { cardA, cardB ->
// A displays above B
if (cardA.isPinned && !cardB.isPinned) {
return@sortedWith -1
}
// B displays above A
if (!cardA.isPinned && cardB.isPinned) {
return@sortedWith 1
}
// At this point, both A & B are pinned or both A & B are non-pinned
// A displays above B since A is newer
if (cardA.updated > cardB.updated) {
return@sortedWith -1
}
// B displays above A since A is newer
if (cardA.updated < cardB.updated) {
return@sortedWith 1
}
0
}
}
)
Personaliza el orden de la fuente de tarjetas modificando directamente la variable estática Attributes.defaults.
1
2
3
4
5
6
7
8
9
10
11
12
13
var attributes = BrazeContentCardUI.ViewController.Attributes.defaults
attributes.transform = { cards in
cards.sorted {
if $0.pinned && !$1.pinned {
return true
} else if !$0.pinned && $1.pinned {
return false
} else {
return $0.createdAt > $1.createdAt
}
}
}
let viewController = BrazeContentCardUI.ViewController(braze: AppDelegate.braze, attributes: attributes)
La personalización mediante BrazeContentCardUI.ViewController.Attributes no está disponible en Objective-C.
Personalizar el mensaje de “fuente vacía”
Cuando un usuario no califica para ninguna Content Cards, el SDK muestra un mensaje de error de “fuente vacía” que dice: “We have no updates. Please check again later.” Puedes personalizar este mensaje de error de “fuente vacía” de forma similar a la siguiente:

El SDK Web no admite reemplazar el idioma de la “fuente vacía” de forma programática. Puedes optar por reemplazarlo cada vez que se muestre la fuente, pero no se recomienda porque la fuente puede tardar un tiempo en actualizarse y el texto de fuente vacía no se mostrará de inmediato.
Si el ContentCardsFragment determina que el usuario no califica para ninguna Content Cards, muestra el mensaje de error de fuente vacía.
Un adaptador especial, el EmptyContentCardsAdapter, reemplaza al ContentCardAdapter estándar para mostrar este mensaje de error. Para establecer el mensaje personalizado, redefine el recurso de cadena com_braze_feed_empty.
El estilo utilizado para mostrar este mensaje se puede encontrar en Braze.ContentCardsDisplay.Empty y se reproduce en el siguiente fragmento de código:
1
2
3
4
5
6
7
8
9
<style name="Braze.ContentCardsDisplay.Empty">
<item name="android:lineSpacingExtra">1.5dp</item>
<item name="android:text">@string/com_braze_feed_empty</item>
<item name="android:textColor">@color/com_braze_content_card_empty_text_color</item>
<item name="android:textSize">18.0sp</item>
<item name="android:gravity">center</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_width">match_parent</item>
</style>
Para obtener más información sobre cómo personalizar los elementos de estilo de Content Cards, consulta Personalizar el estilo.
Para personalizar el mensaje de error de “fuente vacía” con Jetpack Compose, puedes pasar un emptyString a ContentCardsList. También puedes pasar emptyTextStyle a ContentCardListStyling para personalizar aún más este mensaje.
1
2
3
4
5
6
ContentCardsList(
emptyString = "No messages today",
style = ContentCardListStyling(
emptyTextStyle = TextStyle(...)
)
)
Si tienes un Composable que te gustaría mostrar en su lugar, puedes pasar emptyComposable a ContentCardsList. Si se especifica emptyComposable, no se utilizará el emptyString.
1
2
3
4
5
6
7
8
ContentCardsList(
emptyComposable = {
Image(
painter = painterResource(id = R.drawable.noMessages),
contentDescription = "No messages"
)
}
)
Personaliza el estado vacío del controlador de vista configurando los Attributes relacionados.
1
2
3
4
var attributes = BrazeContentCardUI.ViewController.Attributes.defaults
attributes.emptyStateMessage = "This is a custom empty state message"
attributes.emptyStateMessageFont = .preferredFont(forTextStyle: .title1)
attributes.emptyStateMessageColor = .secondaryLabel
Cambia el idioma que aparece automáticamente en las fuentes de Content Cards vacías redefiniendo las cadenas localizables de Content Cards en el archivo ContentCardsLocalizable.strings de tu aplicación.

Si deseas actualizar este mensaje en diferentes idiomas de configuración regional, busca el idioma correspondiente en la estructura de carpetas de recursos con la cadena ContentCardsLocalizable.strings.
Implementar múltiples fuentes
Las Content Cards se pueden filtrar en tu aplicación para que solo se muestren tarjetas específicas, lo que te permite tener múltiples fuentes de Content Cards para diferentes casos de uso. Por ejemplo, puedes mantener tanto una fuente transaccional como una fuente de marketing. Para lograr esto, crea diferentes categorías de Content Cards configurando pares clave-valor en el panel de Braze. Luego, crea fuentes en tu aplicación o sitio que traten estos tipos de Content Cards de manera diferente, filtrando algunos tipos y mostrando otros.
Paso 1: Configurar pares clave-valor en las tarjetas
Al crear una campaña de Content Cards, configura los datos de par clave-valor en cada tarjeta. Utilizarás este par clave-valor para categorizar las tarjetas. Los pares clave-valor se almacenan en la propiedad extras del modelo de datos de la tarjeta.
Para este ejemplo, configuraremos un par clave-valor con la clave feed_type que designará en qué fuente de Content Cards debe mostrarse la tarjeta. El valor será el que corresponda a tus fuentes personalizadas, como home_screen o marketing.
Paso 2: Filtrar Content Cards
Una vez que se hayan asignado los pares clave-valor, crea una fuente con lógica que muestre las tarjetas que deseas mostrar y filtre las tarjetas de otros tipos. En este ejemplo, solo mostraremos las tarjetas con un par clave-valor coincidente de feed_type: "Transactional".
El siguiente ejemplo mostrará la fuente de Content Cards para tarjetas de tipo Transactional:
1
2
3
4
5
6
7
8
9
/**
* @param {String} feed_type - value of the "feed_type" KVP to filter
*/
function showCardsByFeedType(feed_type) {
braze.showContentCards(null, function(cards) {
return cards.filter((card) => card.extras["feed_type"] === feed_type);
});
}
Luego, puedes configurar un alternador para tu fuente personalizada:
1
2
3
4
// show the "Transactional" feed when this button is clicked
document.getElementById("show-transactional-feed").onclick = function() {
showCardsByFeedType("Transactional");
};
Para más información, consulta la documentación de métodos del SDK.
De forma predeterminada, la fuente de Content Cards se muestra en un ContentCardsFragment y IContentCardsUpdateHandler devuelve una lista de tarjetas para mostrar después de recibir un ContentCardsUpdatedEvent del SDK de Braze. Sin embargo, solo ordena las tarjetas y no maneja ningún filtrado directamente.
Paso 2.1: Crear un controlador personalizado
Puedes filtrar Content Cards implementando un IContentCardsUpdateHandler personalizado usando los pares clave-valor configurados por Card.getExtras() en el panel, luego modificándolo para eliminar cualquier tarjeta de la lista que no coincida con el valor de feed_type que configuraste anteriormente.
Mostrar ejemplo en Java
1
2
3
4
5
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
32
33
34
35
36
private IContentCardsUpdateHandler getUpdateHandlerForFeedType(final String desiredFeedType) {
return new IContentCardsUpdateHandler() {
@Override
public List<Card> handleCardUpdate(ContentCardsUpdatedEvent event) {
// Use the default card update handler for a first
// pass at sorting the cards. This is not required
// but is done for convenience.
final List<Card> cards = new DefaultContentCardsUpdateHandler().handleCardUpdate(event);
final Iterator<Card> cardIterator = cards.iterator();
while (cardIterator.hasNext()) {
final Card card = cardIterator.next();
// Make sure the card has our custom KVP
// from the dashboard with the key "feed_type"
if (card.getExtras().containsKey("feed_type")) {
final String feedType = card.getExtras().get("feed_type");
if (!desiredFeedType.equals(feedType)) {
// The card has a feed type, but it doesn't match
// our desired feed type, remove it.
cardIterator.remove();
}
} else {
// The card doesn't have a feed
// type at all, remove it
cardIterator.remove();
}
}
// At this point, all of the cards in this list have
// a feed type that explicitly matches the value we put
// in the dashboard.
return cards;
}
};
}
Mostrar ejemplo en Kotlin
1
2
3
4
5
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
32
33
private fun getUpdateHandlerForFeedType(desiredFeedType: String): IContentCardsUpdateHandler {
return IContentCardsUpdateHandler { event ->
// Use the default card update handler for a first
// pass at sorting the cards. This is not required
// but is done for convenience.
val cards = DefaultContentCardsUpdateHandler().handleCardUpdate(event)
val cardIterator = cards.iterator()
while (cardIterator.hasNext()) {
val card = cardIterator.next()
// Make sure the card has our custom KVP
// from the dashboard with the key "feed_type"
if (card.extras.containsKey("feed_type")) {
val feedType = card.extras["feed_type"]
if (desiredFeedType != feedType) {
// The card has a feed type, but it doesn't match
// our desired feed type, remove it.
cardIterator.remove()
}
} else {
// The card doesn't have a feed
// type at all, remove it
cardIterator.remove()
}
}
// At this point, all of the cards in this list have
// a feed type that explicitly matches the value we put
// in the dashboard.
cards
}
}
Paso 2.2: Añadirlo a un fragmento
Una vez que hayas creado un IContentCardsUpdateHandler, crea un ContentCardsFragment que lo utilice. Esta fuente personalizada se puede usar como cualquier otro ContentCardsFragment. En las diferentes partes de tu aplicación, muestra diferentes fuentes de Content Cards basándote en la clave proporcionada en el panel. Cada fuente de ContentCardsFragment tendrá un conjunto único de tarjetas mostradas gracias al IContentCardsUpdateHandler personalizado en cada fragmento.
Mostrar ejemplo en Java
1
2
3
// We want a Content Cards feed that only shows "Transactional" cards.
ContentCardsFragment customContentCardsFragment = new ContentCardsFragment();
customContentCardsFragment.setContentCardUpdateHandler(getUpdateHandlerForFeedType("Transactional"));
Mostrar ejemplo en Kotlin
1
2
3
// We want a Content Cards feed that only shows "Transactional" cards.
val customContentCardsFragment = ContentCardsFragment()
customContentCardsFragment.contentCardUpdateHandler = getUpdateHandlerForFeedType("Transactional")
Para filtrar qué Content Cards se muestran en esta fuente, usa cardUpdateHandler. Por ejemplo:
1
2
3
4
5
6
7
ContentCardsList(
cardUpdateHandler = {
it.filter { card ->
card.extras["feed_type"] == "Transactional"
}
}
)
The following example will show the Content Cards feed for Transactional type cards:
1
2
// Filter cards by the `Transactional` feed type based on your key-value pair.
let transactionalCards = cards.filter { $0.extras["feed_type"] as? String == "Transactional" }
Para ir un paso más allá, las tarjetas presentadas en el controlador de vista se pueden filtrar configurando la propiedad transform en tu estructura Attributes para mostrar solo las tarjetas filtradas por tus criterios.
1
2
3
4
5
6
7
var attributes = BrazeContentCardUI.ViewController.Attributes.defaults
attributes.transform = { cards in
cards.filter { $0.extras["feed_type"] as? String == "Transactional" }
}
// Pass your attributes containing the transformed cards to the Content Card UI.
let viewController = BrazeContentCardUI.ViewController(braze: AppDelegate.braze, attributes: attributes)
1
2
3
4
5
6
7
// Filter cards by the `Transactional` feed type based on your key-value pair.
NSMutableArray<BRZContentCardRaw *> *transactionalCards = [[NSMutableArray alloc] init];
for (BRZContentCardRaw *card in AppDelegate.braze.contentCards.cards) {
if ([card.extras[@"feed_type"] isEqualToString:@"Transactional"]) {
[transactionalCards addObject:card];
}
}