Introducción
Las etiquetas, la nota condicional y el diseño adaptable de Still & Spoke están preparados. El controlador actual puede mostrar una confirmación de demostración sin enviar una reserva. Lo reemplazarás por una solicitud asíncrona real y una respuesta que refleje lo que el estudio aceptó. Una página aparentemente correcta no es una reserva hasta que el servicio preparado devuelve su confirmación.
Necesitas formularios HTML, diseño CSS básico y objetos, funciones, selección del DOM y eventos de JavaScript. Este laboratorio enseña JSON, async/await, fetch, resultados HTTP, estado pendiente y mensajes que permiten recuperarse. La semántica y las restricciones condicionales están preparadas aquí; hacer el laboratorio anterior ayuda, pero no necesitas sus archivos de VM. Edita solo /home/labex/project/stillspoke/request.js y feedback.js. Conserva los demás archivos, la API, la capacidad y los visitantes existentes. Todas las identidades son sintéticas; no se envía correo ni se cobra. Los 60 minutos son una estimación del autor, no un resultado medido con principiantes.
Usa Workspace para reemplazar archivos completos y Studio para el sitio. Study form es un ejemplo funcional independiente; sus reservas no completan tu página. Studio records lee reservas aceptadas. Practice room es una página de entrenamiento independiente que puede rechazar una solicitud antes de aceptar nada. Cada laboratorio tiene datos propios. No se enseñan el backend preparado, el despliegue en producción, la idempotencia de solicitudes arbitrarias ni el cumplimiento completo de accesibilidad.
Envía una solicitud real y espera su respuesta
En este paso, reemplaza el controlador de demostración por una solicitud POST real, impide activaciones duplicadas mientras está pendiente y muestra una confirmación auténtica del servidor.
En Studio, elige Study form. Con Tab llega a Your name y escribe Example Guest; Tab hasta Email y escribe sample@example.test; Tab hasta Session, escribe C y lee Clay, slowly / 10:00. Pasa con Tab por la casilla sin marcar y la nota opcional hasta Reserve my place. Pulsa Enter una vez. Espera Confirmation SS-… real, elige Studio records y compara nombre, correo y horario. Vuelve con Back to signup y Work form. Ese ejemplo tiene otra base de datos. Tu página solo conserva los visitantes iniciales; su texto de demostración no establece aceptación.
El ejemplo muestra el resultado visible esperado: confirmación con encabezado propio y horario legible. Tu número será diferente.

La API preparada forma parte de este entorno local. Sus campos y reglas públicos son:
| Campo JSON | Significado |
|---|---|
name |
Al menos dos caracteres después de quitar los espacios exteriores. |
email |
Dirección sintética terminada en @example.test. El navegador comprueba primero la sintaxis nativa. |
session |
ID estable publicado: clay-morning para las 10:00 o clay-afternoon para las 14:00. La tarde ya está llena. |
support |
Booleano que indica si se seleccionó asistencia de asiento. |
note |
Cadena de al menos cinco caracteres con asistencia seleccionada; opcional en otro caso. |
Un POST envía una propuesta de reserva a api/bookings. Es una URL relativa a la página de trabajo o de ejemplo actual, por lo que cada una conserva su espacio de negocio. La API devuelve JSON, un formato textual para objetos, cadenas, números y booleanos. Los nombres de propiedades y las cadenas JSON llevan comillas dobles. JSON.stringify(data) convierte el objeto JavaScript en texto JSON; response.json() interpreta la respuesta y la convierte en objeto. La cabecera Content-Type: application/json informa al servicio del contenido del cuerpo. Ni el texto del frontend ni un botón desactivado implementan la regla de capacidad del servicio.
Los estados HTTP distinguen los resultados:
| Estado | Respuesta y hecho que establece |
|---|---|
201 |
booking contiene los campos aceptados, sessionTitle legible y id generado por el servidor. Se aceptó una reserva; su confirmación comienza por SS-. Nunca la inventes. |
422 |
errors asocia nombres de campos con explicaciones. No se reservó nada; corrige los campos marcados. |
409 |
La sesión está llena. No se reservó nada; elige una disponible. |
503 |
La pausa controlada rechazó la solicitud antes de aceptarla. No se reservó nada; es seguro reintentar. |
fetch devuelve una Promise, que representa un resultado futuro. Un controlador async puede usar await para pausar su propio trabajo hasta que se resuelva la Promise, mientras el navegador sigue utilizable. Espera la respuesta HTTP y después la interpretación de JSON. Un rechazo HTTP como 422 o 503 también resuelve fetch; no entra automáticamente en catch. Comprueba el estado explícitamente. Un fallo de red o interpretación puede entrar en catch sin establecer si se aceptó la reserva. El mensaje debe indicar aceptación desconocida y la vía de recuperación Studio records.
El evento submit cubre tanto el botón como Enter en un campo. event.preventDefault() evita el envío normal con navegación para que el controlador envíe JSON. Los campos nativamente inválidos detienen el envío antes de llegar al controlador. form.elements busca por name, no por posición visible ni id HTML. Los campos de texto usan .value; la casilla usa .checked.
Establece pending antes de esperar cualquier trabajo. La protección retorna pronto si llega otro evento mientras hay uno pendiente, y .disabled desactiva visiblemente el botón nativo. Impide la activación repetida de esta página durante una solicitud en curso; no garantiza nada frente a otras páginas o clientes concurrentes arbitrarios. try realiza la solicitud; catch muestra aceptación desconocida si falla; finally restaura el botón y limpia el aviso pasivo en todos los casos. Conserva los valores, sin reiniciar el formulario.
clearErrors, showMessage y showResponse son funciones de feedback.js. Tras analizar el HTML, la página preparada carga con defer, en este orden, scenario.js, validity.js, feedback.js y request.js. El primero aporta datos, el segundo la regla condicional nativa y los dos últimos cooperan en la solicitud y los mensajes. El archivo de respuesta actual atiende un 201 normal; mejorarás los rechazos de campo después.
En Workspace, usa cd para elegir el directorio. cat > request.js reemplaza únicamente ese archivo. El marcador here-document entre comillas JS conserva el contenido literal hasta la línea final que contiene solo JS; pega todo sin las comillas invertidas de Markdown. Al volver el indicador del shell, el archivo está guardado.
cd /home/labex/project/stillspoke
cat > request.js <<'JS'
const form = document.querySelector('form');
const reserveButton = form.querySelector('[type="submit"]');
const progress = document.querySelector('#progress');
let pending = false;
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (pending) return;
clearErrors();
document.querySelector('#result').hidden = true;
const data = {
name: form.elements.name.value,
email: form.elements.email.value,
session: form.elements.session.value,
support: form.elements.support.checked,
note: form.elements.note.value
};
pending = true;
reserveButton.disabled = true;
progress.textContent = 'Sending your request...';
try {
const response = await fetch('api/bookings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const body = await response.json();
showResponse(response.status, body);
} catch (error) {
showMessage('No confirmation received. Acceptance is unknown. Check Studio records; wait for the original attempt to be resolved before a new submission.');
} finally {
pending = false;
reserveButton.disabled = false;
progress.textContent = '';
}
});
JS
Vuelve a Studio, actualiza e introduce Morgan Vale, morgan@example.test, elige Clay, slowly / 10:00 y deja desmarcada la asistencia. Pulsa Tab hasta el botón y Enter una vez. Mientras aparece Sending your request..., el botón está desactivado. Espera a que Submission result muestre Reserved for Morgan Vale, Confirmation SS-… auténtica y el título legible de las 10:00. El mensaje pasivo role="status" nunca recibe foco; la sección ordinaria de resultado puede recibirlo para localizar su encabezado y contenido.
Elige Studio records, compara la reserva completa y conserva Existing Visitor y Avery Reed. El ID de demostración antiguo no debe aparecer como reserva aceptada. Una transferencia HTTP exitosa no basta: la respuesta debe ser realmente 201 y la confirmación debe proceder de su booking aceptado. Autocomprobación: existe un registro aceptado real, una solicitud en curso impide activaciones repetidas y el controlador restaura el botón después.
Asocia el rechazo del servidor con el campo correcto
En este paso, convierte un rechazo de validación real en un error legible del campo sin borrar las demás entradas.
El navegador puede rechazar sintaxis de correo incorrecta, pero el servicio tiene una regla de negocio adicional: las direcciones de práctica deben terminar en @example.test. morgan@other.test tiene sintaxis válida, llega al servidor y produce HTTP 422. Es una solicitud y un rechazo reales, distintos del aviso nativo de campo obligatorio.
Una respuesta de error tiene esta forma; son datos para leer, no un comando:
{
"message": "Please correct the marked fields. Nothing was booked.",
"errors": {
"email": "Use an address ending in @example.test for this practice."
}
}
errors.email se refiere a la clave de API email. Haz la correspondencia mediante form.elements[name], no por orden ni por el primer párrafo de error. Una clave de cadena entre corchetes selecciona la propiedad correspondiente. Object.keys(body.errors) produce un array con sus nombres; for (const name of ...) recorre cada nombre. Una variable let puede reasignarse, de modo que first recuerda el primer campo marcado.
textContent muestra cadenas como texto, sin interpretar el mensaje del servidor como HTML. aria-invalid="true" marca el campo inválido; el párrafo asociado email-error aporta un motivo visible mediante el aria-describedby existente. No sustituyas la ayuda persistente por un color. Limpiar errores antes de otra solicitud evita describir un campo ya corregido con un aviso antiguo. En un 422, muestra primero el resultado global y después enfoca la primera entrada marcada. Para otros resultados, se puede enfocar la sección ordinaria. El aviso pasivo de estado en vivo es otro elemento y no debe forzarse su foco.
La confirmación usa un literal de plantilla: las comillas invertidas rodean el texto y ${booking.id} inserta el valor actual. Así se construye una cadena que textContent muestra literalmente. El número procede del objeto de respuesta, no de uno inventado localmente.
En Workspace, reemplaza feedback.js por estas funciones:
cd /home/labex/project/stillspoke
cat > feedback.js <<'JS'
function clearErrors() {
for (const field of document.querySelectorAll('form [name]')) {
field.removeAttribute('aria-invalid');
const error = document.getElementById(field.name + '-error');
if (error) error.textContent = '';
}
}
function showMessage(text) {
const panel = document.querySelector('#result');
document.querySelector('#message').textContent = text;
panel.hidden = false;
panel.focus();
}
function showResponse(status, body) {
if (status === 201) {
const booking = body.booking;
showMessage(`Reserved for ${booking.name}. Confirmation ${booking.id}. ${booking.sessionTitle}.`);
} else if (status === 422) {
showMessage(body.message);
let first;
for (const name of Object.keys(body.errors)) {
const field = document.querySelector('form').elements[name];
document.getElementById(name + '-error').textContent = body.errors[name];
field.setAttribute('aria-invalid', 'true');
if (!first) first = field;
}
first.focus();
} else {
showMessage(body.message);
}
}
JS
Vuelve a Studio, actualiza e introduce Morgan Vale, morgan@other.test, Clay, slowly / 10:00, asistencia seleccionada y A seat near the entrance. Envía una vez. Debes ver el mensaje global Nothing was booked y un nuevo error específico del correo. El foco pasa a Email, se conservan su etiqueta y ayuda, y nombre, sesión, casilla y nota no cambian. Studio records no debe añadir una reserva para la dirección rechazada. Para mirar registros sin salir del borrador, abre el enlace Studio records en otra página con Open Link in New Tab del navegador y vuelve al formulario original.
Corrige solo la dirección a morgan@example.test y envía de nuevo. Se limpia el error anterior, aparece una confirmación auténtica y la nota llega al registro. Prueba también otro campo: con los demás datos válidos, sustituye el nombre por exactamente dos espacios. minlength="2" nativo cuenta esos caracteres escritos, pero la regla publicada elimina primero los espacios exteriores y exige dos caracteres. El 422 real debe marcar y enfocar Your name, no siempre Email, y mantener correo, sesión y nota. Restaura Morgan Vale. Se permite repetir ese nombre sintético en una segunda reserva aceptada; no es un sistema de unicidad de correo. Conserva los visitantes existentes.
Si escribes por error morgan.example.test sin @, el navegador rechaza antes de POST. Ese aviso es esperado, pero no prueba el 422. Sustitúyelo por morgan@other.test, válido para el navegador, para probar la regla del servidor. Si los mensajes no cambian tras editar, usa cat feedback.js en Workspace, compara el bloque completo, corrige el error y actualiza Studio. No borres datos ni reinicies el servicio por una falta de escritura JavaScript. Autocomprobación: la clave real del servidor se asocia a la entrada y al error legible correctos, las demás entradas sobreviven y se puede corregir y reenviar.
Recupérate de sesiones llenas y una pausa segura
En este paso, distingue una sesión llena de un rechazo previo a la aceptación, conserva el borrador y completa un reintento seguro.
En Studio, actualiza e introduce Casey Brook, casey@example.test, Clay, slowly / 14:00, asistencia seleccionada y A seat near the entrance. Lee el título de las 14:00 en el selector antes de enviar. La primera C selecciona la primera opción Clay coincidente; otra C pasa a la siguiente. Lee la selección visible en vez de asumir que una tecla significa siempre la misma posición.
Envía una vez. El servicio devuelve HTTP 409 porque esa sesión está llena. Lee That session is full. Nothing was booked; choose another session. La entrada permanece y el botón vuelve a estar disponible. No borres al visitante de las 14:00 para liberar sitio. El 409 no necesita correo inválido ni petición mal formada: es un rechazo de negocio a una propuesta correcta.
Cambia solo la sesión a Clay, slowly / 10:00. Elige Practice room; se abre otra página para conservar abierto el formulario original. Es una zona de entrenamiento separada del registro de clientes. Elige Pause next submission y espera Next submission will be refused before a booking is accepted. Cierra la práctica o vuelve al formulario Studio original. No uses Back to signup de la práctica para reemplazar el borrador abierto.
La página separada declara la garantía de rechazo antes de aceptar. Mantén abierto el formulario original al usar este control.

Envía una vez el formulario original. Esta vez el servicio devuelve HTTP 503 y dice explícitamente Nothing was booked. Service paused; please retry. Nombre, dirección, sesión de las 10:00, casilla y nota permanecen. Esa solicitud consume la pausa. Con las mismas entradas, pulsa Enter una vez más sobre Reserve my place. Espera el 201 real y compara en Studio records Casey Brook, el correo, el horario de las 10:00 y la nota. Se conservan los visitantes y todas las reservas anteriores.
El contrato del 503 es limitado: rechaza antes de aceptar una reserva y por eso reintentar es seguro. Perder la conexión o no poder leer la respuesta es distinto. catch indica aceptación desconocida, no Nothing was booked. En ese caso abre Studio records en otra página. Una coincidencia puede ayudar a encontrar una confirmación, pero repetir nombres y valores no siempre identifica el intento que la creó. No envíes otra vez mientras el intento original no esté resuelto. Incluso leer correctamente los registros sin encontrar coincidencias no prueba un rechazo: la solicitud podría seguir en curso. Espera su resolución o consulta al operador del estudio (al instructor en esta práctica). Si no puedes leer registros, espera también al servicio. Solo el rechazo 503 explícitamente previo a la aceptación establece un reintento seguro aquí. No se promete idempotencia ni se enseña un flujo de pagos distribuido.
Para recuperarte de una edición habitual, cambia a Workspace y muestra la solicitud guardada:
cd /home/labex/project/stillspoke
cat request.js
Comprueba la URL relativa exacta api/bookings, la escritura de JSON.stringify, las dos expresiones await y el bloque finally. Si un error los cambió, reemplaza el archivo con el bloque completo del paso 1 y actualiza Studio. Antes de enviar otra vez, resuelve los intentos desconocidos conforme al límite anterior; corregir una errata no establece que la solicitud previa fuera rechazada. Cuando sepas que está resuelta, repite la prueba normal pertinente con otro nombre sintético. La comprobación de solo lectura no instala un controlador sustituto ni envía una reserva real para arreglar tu trabajo.
Autocomprobación: las sesiones llenas y pausas muestran resultados tras finalizar la espera, conservan el borrador, permiten corregir o reintentar con seguridad y nunca muestran un número de aceptación inventado. Se acepta redacción equivalente comprensible. Sigue existiendo una reserva normal aceptada y permanecen intactos la capacidad y los visitantes preparados.
Resumen
Enviaste JSON mediante un POST con espera asíncrona, distinguiste rechazos HTTP de incertidumbre de conexión, protegiste las activaciones pendientes y mostraste la confirmación realmente aceptada. Asociaste errores de servidor a los controles, conservaste entradas tras resultados de lleno y pausa, y usaste registros para orientar la recuperación sin suponer que toda conexión fallida significa que no se aceptó nada.
