1. Introducción y configuración
Capacidades web
Queremos cerrar la brecha de capacidades entre la Web y las plataformas nativas, y facilitar a los desarrolladores la creación de experiencias increíbles en la Web abierta. Creemos firmemente que todos los desarrolladores deben tener acceso a las capacidades que necesitan para crear una excelente experiencia web, y nos comprometemos a crear una Web más capaz.
Sin embargo, existen algunas capacidades, como el acceso al sistema de archivos y la detección de inactividad, que están disponibles de forma nativa, pero no en la Web. Estas capacidades faltantes significan que algunos tipos de apps no se pueden entregar en la Web o son menos útiles.
Diseñaremos y desarrollaremos estas nuevas capacidades de una manera abierta y transparente, utilizando los procesos existentes de estándares de la plataforma web abierta y obteniendo comentarios anticipados de los desarrolladores y otros proveedores de navegadores a medida que iteramos en el diseño, para garantizar un diseño interoperable.
Qué compilarás
En este codelab, experimentarás con varias APIs web que son completamente nuevas o que solo están disponibles detrás de una marca. Por lo tanto, este codelab se enfoca en las APIs en sí y en los casos de uso que desbloquean estas APIs, en lugar de compilar un producto final específico.
Qué aprenderás
En este codelab, aprenderás los mecanismos básicos de varias APIs de vanguardia. Ten en cuenta que estos mecanismos aún no están definidos, y valoramos mucho tus comentarios sobre el flujo de desarrolladores.
Requisitos
Como las APIs que se incluyen en este codelab son realmente de vanguardia, los requisitos para cada API varían. Asegúrate de leer con atención la información de compatibilidad al comienzo de cada sección.
Cómo abordar el codelab
No es necesario que el codelab se complete de forma secuencial. Cada sección representa una API independiente, así que no dudes en elegir lo que más te interese.
2. API de Badging
El objetivo de la API de Badging es llamar la atención de los usuarios sobre lo que sucede en segundo plano. Para simplificar la demostración en este codelab, usemos la API para llamar la atención de los usuarios sobre algo que sucede en primer plano. Luego, puedes hacer la transferencia mental a las cosas que suceden en segundo plano.
Instala Airhorner
Para que esta API funcione, necesitas una PWA instalada en la pantalla principal, por lo que el primer paso es instalar una PWA, como la infame y mundialmente famosa airhorner.com. Presiona el botón Instalar en la esquina superior derecha o usa el menú de tres puntos para instalarla de forma manual.

Aparecerá un mensaje de confirmación. Haz clic en Instalar.

Ahora tienes un nuevo ícono en el dock del sistema operativo. Haz clic en él para iniciar la PWA. Tendrá su propia ventana de la app y se ejecutará en modo independiente.
|
|
Cómo establecer una insignia
Ahora que tienes instalada una PWA, necesitas algunos datos numéricos (las insignias solo pueden contener números) para mostrar en una insignia. Una cosa sencilla de contar en The Air Horner es, suspiro, la cantidad de veces que se ha usado la bocina. De hecho, con la app de Airhorner instalada, prueba la bocina y verifica la insignia. Se incrementa en uno cada vez que tocas la bocina.

¿Cómo funciona? Básicamente, el código es el siguiente:
let hornCounter = 0;
const horn = document.querySelector('.horn');
horn.addEventListener('click', () => {
navigator.setExperimentalAppBadge(++hornCounter);
});
Haz sonar la bocina de aire un par de veces y verifica el ícono de la PWA: se actualizará cada vez que suene la bocina. Así de fácil.

Cómo borrar una insignia
El contador llega hasta 99 y, luego, vuelve a comenzar. También puedes restablecerla de forma manual. Abre la pestaña Console de Herramientas para desarrolladores, pega la siguiente línea y presiona Intro.
navigator.setExperimentalAppBadge(0);
Como alternativa, también puedes quitar la insignia de forma explícita, como se muestra en el siguiente fragmento. El ícono de tu PWA ahora debería verse como al principio, claro y sin una insignia.
navigator.clearExperimentalAppBadge();

Comentarios
¿Qué te pareció esta API? Responde brevemente esta encuesta para ayudarnos:
¿Fue intuitiva la API?
¿Pudiste ejecutar el ejemplo?
¿Tienes algo más que decir? ¿Faltaban funciones? Envíanos tus comentarios en esta breve encuesta. ¡Gracias!
3. API de Native File System
La API de Native File System permite a los desarrolladores crear potentes apps web que interactúan con archivos en el dispositivo local del usuario. Después de que un usuario otorga acceso a una app web, esta API permite que las apps web lean o guarden cambios directamente en archivos y carpetas del dispositivo del usuario.
Cómo leer un archivo
El "Hola, mundo" de la API de Native File System consiste en leer un archivo local y obtener su contenido. Crea un archivo .txt sin formato y escribe texto en él. A continuación, navega a cualquier sitio seguro (es decir, un sitio que se publica a través de HTTPS) como example.com y abre la consola de DevTools. Pega el siguiente fragmento de código en la consola. Como la API de Native File System requiere un gesto del usuario, adjuntamos un controlador de doble clic en el documento. Necesitaremos el identificador de archivo más adelante, por lo que lo convertiremos en una variable global.
document.ondblclick = async () => {
window.handle = await window.chooseFileSystemEntries();
const file = await handle.getFile();
document.body.textContent = await file.text();
};

Cuando hagas doble clic en cualquier parte de la página example.com, aparecerá un selector de archivos.

Selecciona el archivo .txt que creaste antes. Luego, el contenido del archivo reemplazará el contenido real de body de example.com.

Cómo guardar un archivo
A continuación, queremos realizar algunos cambios. Por lo tanto, hagamos que body sea editable pegando el siguiente fragmento de código. Ahora puedes editar el texto como si el navegador fuera un editor de texto.
document.body.contentEditable = true;

Ahora, queremos volver a escribir estos cambios en el archivo original. Por lo tanto, necesitamos un escritor en el identificador de archivo, que podemos obtener pegando el siguiente fragmento en la consola. Una vez más, necesitamos un gesto del usuario, por lo que esta vez esperamos un clic en el documento principal.
document.onclick = async () => {
const writer = await handle.createWriter();
await writer.truncate(0);
await writer.write(0, document.body.textContent);
await writer.close();
};

Cuando ahora hagas clic (no doble clic) en el documento, aparecerá un mensaje de permiso. Cuando otorgues permiso, el contenido del archivo será el que hayas editado en body anteriormente. Para verificar los cambios, abre el archivo en otro editor (o vuelve a iniciar el proceso haciendo doble clic en el documento y volviendo a abrir el archivo).


¡Felicitaciones! Acabas de crear el editor de texto más pequeño del mundo [citation needed].
Comentarios
¿Qué te pareció esta API? Responde brevemente esta encuesta para ayudarnos:
¿Fue intuitiva la API?
¿Pudiste ejecutar el ejemplo?
¿Tienes algo más que decir? ¿Faltaban funciones? Envíanos tus comentarios en esta breve encuesta. ¡Gracias!
4. API de Shape Detection
La API de Shape Detection proporciona acceso a detectores de formas acelerados (p.ej., para rostros humanos) y funciona en imágenes fijas o transmisiones de imágenes en vivo. Los sistemas operativos tienen detectores de funciones eficaces y altamente optimizados, como el FaceDetector de Android. La API de Shape Detection abre estas implementaciones nativas y las expone a través de un conjunto de interfaces de JavaScript.
Actualmente, las funciones compatibles son la detección de rostros a través de la interfaz FaceDetector, la detección de códigos de barras a través de la interfaz BarcodeDetector y la detección de texto (reconocimiento óptico de caracteres) a través de la interfaz TextDetector.
Detección de rostro
Una función fascinante de la API de Shape Detection es la detección de rostros. Para probarla, necesitamos una página con rostros. Esta página con el rostro del autor es un buen comienzo. Se verá similar a la captura de pantalla que se muestra a continuación. En un navegador compatible, se reconocerá el cuadro delimitador del rostro y los puntos de referencia faciales.
Puedes ver la poca cantidad de código que se necesitó para que esto sucediera bifurcando o editando el proyecto de Glitch, en especial el archivo script.js.

Si quieres que sea completamente dinámico y no solo trabajar con el rostro del autor, ve a esta página de resultados de la Búsqueda de Google llena de rostros en una pestaña privada o en modo de invitado. Ahora, en esa página, abre las Herramientas para desarrolladores de Chrome haciendo clic con el botón derecho en cualquier lugar y, luego, en Inspeccionar. A continuación, en la pestaña Console, pega el siguiente fragmento. El código destacará los rostros detectados con un cuadro rojo semitransparente.
document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
try {
const faces = await new FaceDetector().detect(img);
faces.forEach(face => {
const div = document.createElement('div');
const box = face.boundingBox;
const computedStyle = getComputedStyle(img);
const [top, right, bottom, left] = [
computedStyle.marginTop,
computedStyle.marginRight,
computedStyle.marginBottom,
computedStyle.marginLeft
].map(m => parseInt(m, 10));
const scaleX = img.width / img.naturalWidth;
const scaleY = img.height / img.naturalHeight;
div.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
div.style.position = 'absolute';
div.style.top = `${scaleY * box.top + top}px`;
div.style.left = `${scaleX * box.left + left}px`;
div.style.width = `${scaleX * box.width}px`;
div.style.height = `${scaleY * box.height}px`;
img.before(div);
});
} catch(e) {
console.error(e);
}
});
Notarás que hay algunos mensajes de DOMException y que no se están procesando todas las imágenes. Esto se debe a que las imágenes above-the-fold se insertan como URIs de datos y, por lo tanto, se puede acceder a ellas, mientras que las imágenes below-the-fold provienen de un dominio diferente que no está configurado para admitir CORS. Para los fines de la demostración, no necesitamos preocuparnos por esto.
Detección de puntos de referencia faciales
Además de los rostros en sí, macOS también admite la detección de puntos de referencia faciales. Para probar la detección de puntos de referencia faciales, pega el siguiente fragmento en la consola. Recordatorio: La alineación de los puntos de referencia no es perfecta en absoluto debido a crbug.com/914348, pero puedes ver hacia dónde se dirige y lo potente que puede ser esta función.
document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
try {
const faces = await new FaceDetector().detect(img);
faces.forEach(face => {
const div = document.createElement('div');
const box = face.boundingBox;
const computedStyle = getComputedStyle(img);
const [top, right, bottom, left] = [
computedStyle.marginTop,
computedStyle.marginRight,
computedStyle.marginBottom,
computedStyle.marginLeft
].map(m => parseInt(m, 10));
const scaleX = img.width / img.naturalWidth;
const scaleY = img.height / img.naturalHeight;
div.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
div.style.position = 'absolute';
div.style.top = `${scaleY * box.top + top}px`;
div.style.left = `${scaleX * box.left + left}px`;
div.style.width = `${scaleX * box.width}px`;
div.style.height = `${scaleY * box.height}px`;
img.before(div);
const landmarkSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
landmarkSVG.style.position = 'absolute';
landmarkSVG.classList.add('landmarks');
landmarkSVG.setAttribute('viewBox', `0 0 ${img.width} ${img.height}`);
landmarkSVG.style.width = `${img.width}px`;
landmarkSVG.style.height = `${img.height}px`;
face.landmarks.map((landmark) => {
landmarkSVG.innerHTML += `<polygon class="landmark-${landmark.type}" points="${
landmark.locations.map((point) => {
return `${scaleX * point.x},${scaleY * point.y} `;
}).join(' ')
}" /></svg>`;
});
div.before(landmarkSVG);
});
} catch(e) {
console.error(e);
}
});
Detección de códigos de barras
La segunda función de la API de Shape Detection es la detección de códigos de barras. Al igual que antes, necesitamos una página con códigos de barras, como esta. Cuando lo abras en un navegador, verás los distintos códigos QR descifrados. Remixa o edita el proyecto de Glitch, en especial el archivo script.js, para ver cómo se hace.

Si quieres algo más dinámico, puedes volver a usar la Búsqueda de imágenes de Google. Esta vez, en tu navegador, navega a esta página de resultados de la Búsqueda de Google en una pestaña privada o en modo de invitado. Ahora pega el siguiente fragmento en la pestaña Consola de las Herramientas para desarrolladores de Chrome. Después de un breve momento, los códigos de barras reconocidos se anotarán con el valor sin procesar y el tipo de código de barras.
document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
try {
const barcodes = await new BarcodeDetector().detect(img);
barcodes.forEach(barcode => {
const div = document.createElement('div');
const box = barcode.boundingBox;
const computedStyle = getComputedStyle(img);
const [top, right, bottom, left] = [
computedStyle.marginTop,
computedStyle.marginRight,
computedStyle.marginBottom,
computedStyle.marginLeft
].map(m => parseInt(m, 10));
const scaleX = img.width / img.naturalWidth;
const scaleY = img.height / img.naturalHeight;
div.style.backgroundColor = 'rgba(255, 255, 255, 0.75)';
div.style.position = 'absolute';
div.style.top = `${scaleY * box.top + top}px`;
div.style.left = `${scaleX * box.left - left}px`;
div.style.width = `${scaleX * box.width}px`;
div.style.height = `${scaleY * box.height}px`;
div.style.color = 'black';
div.style.fontSize = '14px';
div.textContent = `${barcode.rawValue}`;
img.before(div);
});
} catch(e) {
console.error(e);
}
});
Detección de texto
La última función de la API de Shape Detection es la detección de texto. A estas alturas, ya sabes cómo funciona: necesitamos una página con imágenes que contengan texto, como esta con resultados de escaneo de Google Books. En los navegadores compatibles, verás el texto reconocido y un cuadro delimitador dibujado alrededor de los pasajes de texto. Remixa o edita el proyecto de Glitch, en especial el archivo script.js, para ver cómo se hace.

Para probar esto de forma dinámica, dirígete a esta página de resultados de la Búsqueda en una pestaña privada o en modo de invitado. Ahora pega el siguiente fragmento en la pestaña Consola de las Herramientas para desarrolladores de Chrome. Con un poco de espera, se reconocerá parte del texto.
document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
try {
const texts = await new TextDetector().detect(img);
texts.forEach(text => {
const div = document.createElement('div');
const box = text.boundingBox;
const computedStyle = getComputedStyle(img);
const [top, right, bottom, left] = [
computedStyle.marginTop,
computedStyle.marginRight,
computedStyle.marginBottom,
computedStyle.marginLeft
].map(m => parseInt(m, 10));
const scaleX = img.width / img.naturalWidth;
const scaleY = img.height / img.naturalHeight;
div.style.backgroundColor = 'rgba(255, 255, 255, 0.75)';
div.style.position = 'absolute';
div.style.top = `${scaleY * box.top + top}px`;
div.style.left = `${scaleX * box.left - left}px`;
div.style.width = `${scaleX * box.width}px`;
div.style.height = `${scaleY * box.height}px`;
div.style.color = 'black';
div.style.fontSize = '14px';
div.innerHTML = text.rawValue;
img.before(div);
});
} catch(e) {
console.error(e);
}
});
Comentarios
¿Qué te pareció esta API? Responde brevemente esta encuesta para ayudarnos:
¿Fue intuitiva la API?
¿Pudiste ejecutar el ejemplo?
¿Tienes algo más que decir? ¿Faltaban funciones? Envíanos tus comentarios en esta breve encuesta. ¡Gracias!
5. API de Web Share Target
La API de Web Share Target permite que las apps web instaladas se registren en el sistema operativo subyacente como destino de uso compartido para recibir contenido compartido desde la API de Web Share o eventos del sistema, como el botón de uso compartido a nivel del sistema operativo.
Cómo instalar una AWP para compartir contenido
Como primer paso, necesitas una PWA para compartir. Esta vez, Airhorner (por suerte) no servirá, pero la app de demostración de Web Share Target te ayudará. Instala la app en la pantalla principal de tu dispositivo.

Cómo compartir contenido en la AWP
A continuación, necesitas algo para compartir, como una foto de Google Fotos. Usa el botón Compartir y selecciona la PWA de Recortes como destino para compartir.

Cuando presiones el ícono de la app, accederás directamente a la AWP de Recortes, y la foto estará allí.

¿Cómo funciona? Para averiguarlo, explora el manifiesto de la app web de la AWP de Scrapbook. La configuración para que funcione la API de Web Share Target se encuentra en la propiedad "share_target" del manifiesto que, en su campo "action", apunta a una URL que se decora con parámetros como se indica en "params".
Luego, el lado del uso compartido completa esta plantilla de URL según corresponda (ya sea a través de una acción de uso compartido o controlada de forma programática por el desarrollador con la API de Web Share), de modo que el lado receptor pueda extraer los parámetros y hacer algo con ellos, como mostrarlos.
{
"action": "/_share-target",
"enctype": "multipart/form-data",
"method": "POST",
"params": {
"files": [{
"name": "media",
"accept": ["audio/*", "image/*", "video/*"]
}]
}
}
Comentarios
¿Qué te pareció esta API? Responde brevemente esta encuesta para ayudarnos:
¿Fue intuitiva la API?
¿Pudiste ejecutar el ejemplo?
¿Tienes algo más que decir? ¿Faltaban funciones? Envíanos tus comentarios en esta breve encuesta. ¡Gracias!
6. API de Wake Lock
Para evitar que se agote la batería, la mayoría de los dispositivos se suspenden rápidamente cuando están inactivos. Si bien esto es correcto la mayoría de las veces, algunas aplicaciones necesitan mantener la pantalla o el dispositivo activos para completar su trabajo. La API de Wake Lock proporciona una forma de evitar que el dispositivo atenúe y bloquee la pantalla o que entre en modo de suspensión. Esta capacidad permite crear nuevas experiencias que, hasta ahora, requerían una app nativa.
Configura un protector de pantalla
Para probar la API de Wake Lock, primero debes asegurarte de que el dispositivo se suspendería. Por lo tanto, en el panel de preferencias de tu sistema operativo, activa el protector de pantalla que prefieras y asegúrate de que se inicie después de 1 minuto. Para asegurarte de que funcione, deja el dispositivo solo durante ese tiempo exacto (sí, lo sé, es doloroso). Las capturas de pantalla que se muestran a continuación son de macOS, pero, por supuesto, puedes probar esto en tu dispositivo móvil Android o en cualquier plataforma de escritorio compatible.

Cómo establecer un bloqueo de activación de pantalla
Ahora que sabes que el protector de pantalla funciona, usarás un bloqueo de activación de tipo "screen" para evitar que el protector de pantalla haga su trabajo. Dirígete a la app de demostración de Wake Lock y haz clic en Activar .
Casilla de verificación screen Wake Lock

A partir de ese momento, se activa un bloqueo de activación. Si tienes la paciencia suficiente para dejar el dispositivo sin tocar durante un minuto, verás que el protector de pantalla no se inició.
¿Cómo funciona? Para averiguarlo, dirígete al proyecto de Glitch de la app de demostración de Wake Lock y consulta script.js. El fragmento de código que se muestra a continuación contiene la esencia del código. Abre una pestaña nueva (o usa cualquier pestaña que tengas abierta) y pega el siguiente código en una consola de Herramientas para desarrolladores de Chrome. Cuando hagas clic en la ventana, deberías ver un bloqueo de activación que está activo durante exactamente 10 segundos (consulta los registros de la consola) y el protector de pantalla no debería iniciarse.
if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
let wakeLock = null;
const requestWakeLock = async () => {
try {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
console.log('Wake Lock was released');
});
console.log('Wake Lock is active');
} catch (e) {
console.error(`${e.name}, ${e.message}`);
}
};
requestWakeLock();
window.setTimeout(() => {
wakeLock.release();
}, 10 * 1000);
}

Comentarios
¿Qué te pareció esta API? Responde brevemente esta encuesta para ayudarnos:
¿Fue intuitiva la API?
¿Pudiste ejecutar el ejemplo?
¿Tienes algo más que decir? ¿Faltaban funciones? Envíanos tus comentarios en esta breve encuesta. ¡Gracias!
7. API de Contact Picker
Una API que nos entusiasma mucho es la API de Contact Picker. Permite que una app web acceda a los contactos desde el administrador de contactos nativo del dispositivo, de modo que tu app web tenga acceso a los nombres, las direcciones de correo electrónico y los números de teléfono de tus contactos. Puedes especificar si quieres solo uno o varios contactos, y si quieres todos los campos o solo un subconjunto de nombres, direcciones de correo electrónico y números de teléfono.
Consideraciones de privacidad
Una vez que se abra el selector, podrás seleccionar los contactos con los que quieras compartir contenido. Observarás que no hay una opción para seleccionar todo, lo que es deliberado: queremos que el uso compartido sea una decisión consciente. Del mismo modo, el acceso no es continuo, sino una decisión única.
Cómo acceder a los contactos
Acceder a los contactos es una tarea sencilla. Antes de que se abra el selector, puedes especificar qué campos deseas (las opciones son name, email y telephone) y si deseas acceder a varios contactos o solo a uno. Para probar esta API en un dispositivo Android, abre la aplicación de demostración. La sección pertinente del código fuente es, básicamente, el siguiente fragmento:
getContactsButton.addEventListener('click', async () => {
const contacts = await navigator.contacts.select(
['name', 'email'],
{multiple: true});
if (!contacts.length) {
// No contacts were selected, or picker couldn't be opened.
return;
}
console.log(contacts);
});

8. API de Async Clipboard
Cómo copiar y pegar texto
Hasta ahora, no había forma de copiar y pegar imágenes en el portapapeles del sistema de forma programática. Recientemente, agregamos compatibilidad con imágenes a la API de Async Clipboard.
para que ahora puedas copiar y pegar imágenes. La novedad es que también puedes escribir imágenes en el portapapeles. La API de portapapeles asíncrona admite la copia y el pegado de texto desde hace un tiempo. Puedes copiar texto en el portapapeles llamando a navigator.clipboard.writeText() y, luego, pegar ese texto llamando a navigator.clipboard.readText().
Cómo copiar y pegar imágenes
Ahora también puedes escribir imágenes en el portapapeles. Para que esto funcione, necesitas los datos de la imagen como un blob que luego pasas al constructor del elemento del portapapeles. Por último, puedes copiar este elemento del portapapeles llamando a navigator.clipboard.write().
// Copy: Writing image to the clipboard
try {
const imgURL = 'https://developers.google.com/web/updates/images/generic/file.png';
const data = await fetch(imgURL);
const blob = await data.blob();
await navigator.clipboard.write([
new ClipboardItem(Object.defineProperty({}, blob.type, {
value: blob,
enumerable: true
}))
]);
console.log('Image copied.');
} catch(e) {
console.error(e, e.message);
}
Pegar la imagen desde el portapapeles parece un proceso complejo, pero, en realidad, solo consiste en recuperar el BLOB del elemento del portapapeles. Como puede haber varios, debes iterar sobre ellos hasta que encuentres el que te interesa. Por motivos de seguridad, por el momento, esta función se limita a las imágenes PNG, pero es posible que se admitan más formatos de imagen en el futuro.
async function getClipboardContents() {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
try {
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
console.log(URL.createObjectURL(blob));
}
} catch (e) {
console.error(e, e.message);
}
}
} catch (e) {
console.error(e, e.message);
}
}
Puedes ver esta API en acción en una app de demostración. Los fragmentos relevantes de su código fuente se incorporan más arriba. Puedes copiar imágenes en el portapapeles sin permiso, pero debes otorgar acceso para pegar desde el portapapeles.

Después de otorgar acceso, puedes leer la imagen del portapapeles y pegarla en la aplicación:

9. ¡Lo lograste!
¡Felicitaciones! Llegaste al final del codelab. Una vez más, te recordamos que la mayoría de las APIs aún están en proceso y se está trabajando en ellas de forma activa. Por lo tanto, el equipo agradece mucho tus comentarios, ya que solo la interacción con personas como tú nos ayudará a que estas APIs sean correctas.
También te recomendamos que consultes con frecuencia nuestra página de destino de Capacidades. Lo mantendremos actualizado y contiene punteros a todos los artículos detallados de las APIs en las que trabajamos. ¡Sigue rockeando!
Tom y todo el equipo de Capabilities 🐡
