Contrato de la API

Las cuatro rutas que expone tu backend, para cuando quieras construirle algo encima. Todas cuelgan de la URL que te imprime npm run deploy.

Esto describe el contrato que exige la spec y que revisa npm run verify 3. Si tu agente escribió algo distinto, compáralo contra esta página antes de desplegar.

Cómo se ve un elemento guardado

{
  "altarId": "mi-altar",
  "id": "9f0c1e7a-...",
  "tipo": "vela",
  "nivel": 1,
  "posicion": 0,
  "texto": "Para mi abuela",
  "link": null,
  "imageKey": null,
  "puestoPor": "Axel",
  "creadoEn": "2026-10-29T03:14:07.000Z"
}

Tres campos los pone el servidor, no tú

  • id es un UUID que genera la Lambda.
  • creadoEn es la fecha del servidor, en ISO.
  • posicion es el orden dentro de su grada, y sale de contar cuántos hay ya en ese nivel. Si lo mandas en el cuerpo, se ignora.

Eso es a propósito: son los tres campos donde un cliente mentiroso haría daño.

GET /elements

Lista los elementos de un altar. Recibe altarId como parámetro de query.

Por dentro es un Query de DynamoDB con Limit, no un Scan. La diferencia importa: Scan lee la tabla entera y te cobra por todo, Query lee solo las filas de tu altar.

POST /elements

Hace dos cosas, según lo que le mandes.

  • Para agregar, el cuerpo lleva al menos altarId y tipo. Responde 201 con el elemento guardado.
  • Para quitar, el cuerpo lleva altarId, borrar: true y el id. Como la sort key es <creadoEn>#<id>, la Lambda primero encuentra la fila con un Query por altarId y luego la borra. Responde 404 si ese id no existe en el altar.

💡 Solo seis campos del cuerpo pueden entrar a la tabla

tipo, nivel, texto, link, puestoPor e imageKey.

Lo que no esté en esa lista se descarta, aunque lo mandes. posicion también se ignora porque la Lambda la calcula contando lo que ya existe en esa grada.

Responde 400 si el cuerpo no es JSON válido, si falta altarId, o si falta tipo al agregar.

⚠️ Esta ruta está abierta

No tiene autenticación. Cualquiera con la URL de tu API puede escribir en tu altar.

Lo que hace la Lambda para limitar el daño es copiar solo seis campos conocidos; id, creadoEn y posicion los calcula el servidor. Cerrarla de verdad pide un autorizador de Cognito en esta ruta, y eso ya no cabe en el taller.

POST /uploads

Pide una URL firmada para subir una imagen a S3. Recibe altarId y el contentType.

La URL sirve para un PUT y vive cinco minutos. Responde 400 si el contentType no es de imagen.

Tu navegador sube el archivo directo a S3 con esa URL. La imagen no pasa por la Lambda, y por eso puedes subir fotos grandes sin que se quede sin memoria.

GET /uploads/{proxy+}

Pide una URL firmada para leer una imagen. Vive hasta una hora; puede expirar antes si terminan primero las credenciales temporales que la firmaron.

Esta ruta existe porque el bucket es privado. En vez de abrir el bucket al mundo, se firma cada lectura. Es la forma que recomienda AWS para objetos privados.

La misma Lambda atiende esta ruta y la de arriba: una firma para escribir, la otra para leer.

La llave de la tabla

Partition key altarId. Sort key sk, con la forma <creadoEn>#<id>.

La fecha va primero porque en ISO ordena como texto, así que un Query te devuelve el altar en orden de creación sin ordenar nada después. El id va al final para desempatar dos elementos del mismo milisegundo.

sk no le sirve de nada al frontend, así que la respuesta del POST la quita antes de contestar.

⚠️ Si cambias la llave, se rompe el listado

Es la única decisión del backend que no se puede cambiar sin rehacer la tabla. La steering del starter se lo prohíbe al agente por eso.