Actividad — Tarjeta de Perfil con la API de GitHub
E-AWOS-2 · Unidad II · HTML + JavaScript · Tarea individual
¿Qué vas a construir?
Un archivo [Link] que, dado un nombre de usuario de GitHub, consulta la API oficial y muestra una
tarjeta de perfil: foto, nombre, bio y estadísticas. Sin servidor, sin npm, sin frameworks.
Conceptos nuevos
Personal Access Token Primera vez que autentican una petición con un header Authorization.
fetch() con headers Patrón que se repite en prácticamente todas las APIs reales.
Respuesta JSON anidada Campos opcionales que pueden ser null; hay que manejarlos.
textContent vs innerHTML Seguridad: nunca inyectar datos externos con innerHTML.
■ Esta actividad usa un Personal Access Token. Trata el token como una contraseña: nunca lo subas a un
repositorio público.
1 — Preparación: cuenta de GitHub y token
Si ya tienes cuenta de GitHub, salta directo al Paso 2. Si no, ve a [Link] haz clic en Sign up
y sigue el formulario. Verifica tu correo antes de continuar.
Generar el Personal Access Token (PAT)
Sigue estos pasos en orden. No te saltes ninguno.
1 Inicia sesión en GitHub.
2 Haz clic en tu foto de perfil (esquina superior derecha) → Settings.
3 En el menú izquierdo, baja al final → Developer settings.
4 Clic en Personal access tokens → Tokens (classic).
5 Clic en Generate new token → Generate new token (classic).
6 En Note escribe: Actividad AWOS2
7 En Expiration selecciona 7 days.
8 En Select scopes marca únicamente: public_repo
9 Clic en Generate token (botón verde al final de la página).
10 Copia el token ahora. Solo se muestra una vez; si cierras la página tendrás que generar uno nuevo.
■ Si ya cerraste la página sin copiar el token, repite desde el Paso 5. No hay forma de recuperarlo después.
2 — El endpoint de la API
La API de GitHub expone un endpoint para obtener datos de cualquier usuario público:
Endpoint
GET [Link]
Ejemplo:
GET [Link]
Campos que usarás de la respuesta JSON
login Nombre de usuario
name Nombre real (puede ser null)
avatar_url URL de la foto de perfil
bio Biografía (puede ser null)
public_repos Número de repositorios públicos
followers Número de seguidores
following Número de cuentas que sigue
html_url URL del perfil en GitHub
¿Por qué se necesita el token si el perfil es público? Sin autenticación, la API permite solo 60 peticiones
por hora desde una misma IP. En un salón de clases eso se agota rápido. Con token el límite sube a 5,000 por
hora por usuario. Además, autenticar con un header es el objetivo central de esta actividad.
3 — El código completo
■ Busca la línea const TOKEN = 'TU_TOKEN_AQUI'; y reemplaza TU_TOKEN_AQUI con el token que
generaste. Asegúrate de que quede entre comillas simples.
Parte 1 — <head> y estilos CSS
[Link] — Parte 1
<!DOCTYPE html>
<html lang='es'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Tarjeta de Perfil GitHub</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #0d1117; color: #e6edf3;
min-height: 100vh; display: flex;
flex-direction: column; align-items: center; padding: 40px 20px;
h1 { font-size: 1.4rem; margin-bottom: 24px; color: #8b949e; font-weight: 400; }
.busqueda { display: flex; gap: 8px; margin-bottom: 32px;
width: 100%; max-width: 480px; }
input { flex: 1; padding: 10px 14px; border-radius: 6px;
border: 1px solid #30363d; background: #161b22;
color: #e6edf3; font-size: 1rem; outline: none; }
input:focus { border-color: #58a6ff; }
button { padding: 10px 18px; border-radius: 6px; border: none;
background: #238636; color: white; font-size: 1rem; cursor: pointer; }
button:hover { background: #2ea043; }
button:disabled { background: #1a3a21; color: #6e7681; cursor: not-allowed; }
.cargando { color: #8b949e; font-size: .95rem; display: none; }
.[Link] { display: block; }
.card { background: #161b22; border: 1px solid #30363d;
border-radius: 12px; padding: 28px;
max-width: 480px; width: 100%; display: none; }
.[Link] { display: block; }
.card-header { display: flex; gap: 16px; align-items: center;
margin-bottom: 16px; }
.avatar { width: 72px; height: 72px; border-radius: 50%;
border: 2px solid #30363d; flex-shrink: 0; }
.nombre { font-size: 1.2rem; font-weight: bold; margin-bottom: 2px; }
.usuario { color: #8b949e; font-size: .95rem; }
.bio { color: #8b949e; font-size: .9rem; margin-bottom: 16px;
line-height: 1.5; }
.stats { display: flex; gap: 24px; font-size: .9rem;
border-top: 1px solid #30363d; padding-top: 14px;
margin-bottom: 14px; }
.stat-valor { font-weight: bold; font-size: 1rem; }
.stat-label { color: #8b949e; }
.enlace { display: inline-block; color: #58a6ff;
font-size: .9rem; text-decoration: none; }
.enlace:hover { text-decoration: underline; }
.error { background: #1a0000; border: 1px solid #f85149;
border-radius: 8px; padding: 14px 18px; color: #f85149;
max-width: 480px; width: 100%; display: none; }
.[Link] { display: block; }
</style>
</head>
Parte 2 — <body> (estructura HTML)
[Link] — Parte 2
<body>
<h1>Buscador de perfiles GitHub</h1>
<div class='busqueda'>
<input type='text' id='inputUsuario'
placeholder='Escribe un usuario de GitHub...'>
<button id='btnBuscar'>Buscar</button>
</div>
<p class='cargando' id='cargando'>Buscando...</p>
<div class='error' id='mensajeError'></div>
<div class='card' id='tarjeta'>
<div class='card-header'>
<!-- src se asigna con setAttribute(), no con innerHTML -->
<img class='avatar' id='avatar' src='' alt='Avatar'>
<div>
<div class='nombre' id='nombre'></div>
<div class='usuario' id='usuario'></div>
</div>
</div>
<div class='bio' id='bio'></div>
<div class='stats'>
<div>
<div class='stat-valor' id='repos'></div>
<div class='stat-label'>Repositorios</div>
</div>
<div>
<div class='stat-valor' id='seguidores'></div>
<div class='stat-label'>Seguidores</div>
</div>
<div>
<div class='stat-valor' id='siguiendo'></div>
<div class='stat-label'>Siguiendo</div>
</div>
</div>
<!-- href se asigna con setAttribute() -->
<a class='enlace' id='enlace' href='' target='_blank'>
Ver perfil completo en GitHub →
</a>
</div>
Parte 3 — <script> (lógica JavaScript)
Lee cada comentario antes de copiar. Explican el por qué, no solo el qué.
[Link] — Parte 3
<script>
// ■■ Configuración ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// Reemplaza TU_TOKEN_AQUI con tu PAT.
// Antes de subir al repo, vuelve a poner TU_TOKEN_AQUI aquí.
const TOKEN = 'TU_TOKEN_AQUI';
// ■■ Referencias al DOM ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
const btn = [Link]('btnBuscar');
const input = [Link]('inputUsuario');
const tarjeta = [Link]('tarjeta');
const errorDiv = [Link]('mensajeError');
const cargandoDiv = [Link]('cargando');
// ■■ Función principal ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
async function buscarUsuario() {
const username = [Link]();
if (!username) return;
ocultarTarjeta(); ocultarError();
mostrarCargando(true); [Link] = true;
// fetch con headers de autenticación.
// await pausa la función sin bloquear el navegador.
const respuesta = await fetch(
`[Link]
headers: {
// Bearer TOKEN: patrón estándar de autenticación.
'Authorization': `Bearer ${TOKEN}`,
'Accept': 'application/[Link]+json'
);
mostrarCargando(false); [Link] = false;
// IMPORTANTE: fetch NO lanza error en respuestas 4xx/5xx.
// Hay que revisar [Link] de forma explícita.
if (![Link]) {
if ([Link] === 404) {
mostrarError('Usuario no encontrado. Verifica el nombre.');
} else if ([Link] === 401) {
mostrarError('Token inválido. Revisa que lo copiaste completo.');
} else if ([Link] === 403) {
mostrarError('Token expirado o sin permisos. Genera uno nuevo.');
} else {
mostrarError(`Error ${[Link]}. Intenta de nuevo.`);
return;
// .json() también es una Promise, por eso necesita await.
const data = await [Link]();
mostrarTarjeta(data);
// ■■ Llenar y mostrar la tarjeta ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
function mostrarTarjeta(data) {
// setAttribute para la imagen, nunca innerHTML con datos externos.
[Link]('avatar').setAttribute('src', data.avatar_url);
// textContent escapa caracteres especiales automáticamente.
// ?? devuelve el lado derecho si el valor es null o undefined.
[Link]('nombre').textContent = [Link] ?? 'Sin nombre';
[Link]('usuario').textContent = '@' + [Link];
[Link]('bio').textContent = [Link] ?? 'Sin biografía';
[Link]('repos').textContent = data.public_repos;
[Link]('seguidores').textContent = [Link];
[Link]('siguiendo').textContent = [Link];
[Link]('enlace').setAttribute('href', data.html_url);
[Link]('visible');
// ■■ Auxiliares ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
function mostrarError(msg) {
[Link] = msg;
[Link]('visible');
function ocultarError() { [Link]('visible'); }
function ocultarTarjeta() { [Link]('visible'); }
function mostrarCargando(v) { [Link]('visible', v); }
// ■■ Eventos ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
[Link]('click', buscarUsuario);
[Link]('keydown', e => {
if ([Link] === 'Enter') buscarUsuario();
});
</script>
</body>
</html>
4 — Errores comunes
401 Unauthorized Token incorrecto o con espacios. Verifica que copiaste el token completo.
403 Forbidden Token expirado o sin el scope public_repo. Genera uno nuevo.
404 Not Found El usuario no existe. El nombre es case-sensitive en GitHub.
'undefined' visible Accediste a un campo null sin usar ??. Revisa bio, name, location.
Avatar no carga Error tipográfico en el src. Usa setAttribute('src', data.avatar_url).
Cómo depurar con DevTools
Abre el navegador con F12, ve a la pestaña Network y busca la petición a [Link]. Ahí puedes ver
el status HTTP, los headers que se enviaron y el JSON exacto que devolvió la API.
5 — Entrega segura del token
El repositorio de tu portafolio es público. Si subes el archivo con el token real, cualquiera puede usarlo para
actuar como tú en GitHub. Existen bots que escanean repositorios en busca de tokens en cuestión de
segundos.
Procedimiento
1. Termina la actividad y verifica que funciona con tu token.
2. Antes de guardar la versión final, reemplaza el token:
Cambia esto: const TOKEN = 'ghp_xxxxxxxxxxxxxxxxxxxx';
Por esto: const TOKEN = 'TU_TOKEN_AQUI';
3. Guarda y sube el archivo al repositorio.
■ Si ya subiste el token accidentalmente: ve a GitHub → Settings → Developer settings → Personal access
tokens → Delete. Revócalo de inmediato y genera uno nuevo.
6 — Checklist antes de entregar
■ El archivo se llama [Link] y abre directamente en el navegador.
■ Al ingresar un usuario válido aparece la tarjeta con foto, nombre, bio y estadísticas.
■ Si el usuario no existe, aparece un mensaje de error en la página (no solo en consola).
■ El campo bio muestra 'Sin biografía' cuando el usuario no tiene bio.
■ Se puede buscar otro usuario sin recargar la página.
■ El token NO aparece en el código entregado (está como TU_TOKEN_AQUI).
Extensión opcional (sin calificación extra)
Ubicación Muestra [Link] con un ícono ■. Maneja el caso null.
Indicador de carga Muestra 'Buscando...' entre el clic y la respuesta. Ocúltalo al terminar.
Último repositorio Segunda petición: /users/{username}/repos?sort=updated&per;_page=1
E-AWOS-2 · Unidad II — Implementación de APIs de Terceros · Actividad: Tarjeta de Perfil GitHub · Grupo A Bilingüe