00-day-brief.md
00 — Day brief · 2026-08-16 · CESTA
Decisiones ALS-2 BRIEF
| Campo | Valor |
|---|---|
| Fecha | 2026-08-16 |
| Nombre | CESTA |
| Complejidad | Nivel 1 (no L2 consecutivo tras RONDA) |
| Sector | ONG / despensa vecinal (en especie) |
| Tipo | Tabla densa de necesidades + compromiso + inbox STAFF JWT |
| Plataforma | Web responsive (tabla + rail · desktop staff) |
| Registro | R-CL — civic/botánico excelente, no feed social |
| Shell | S-RAIL — rail de iconos + panel de tabla |
| Home | H-TABLE — la tabla es la home |
| Flujo | F-LEAD — compromiso público → cola staff |
| Por qué no RONDA | No S-DOCK / H-FEED / F-BOOK; no gaming; no cronología de eventos |
| Por qué no DERIVA | No S-SPLIT / H-MAP / F-MAP |
Terna unicidad
R-CL · S-RAIL · H-TABLE · F-LEAD
0 códigos iguales a N−1 (RONDA). ≥2 distintos vs N−2 (DERIVA). Ventana 4 días ya tenía R-HY e R-IN; hoy R-CL. Experimento §8: tabla densa / ONG.
Mood visual (Paper)
- Candidatos: charity cream+naranja (primer instinto), chapel, botanical, mineral, bookish
- Elegido: botanical — no el primer instinto (warm off-white × terracotta, cliché y vetado)
- Paleta:
#E6EFDDhueso ·#1C2418tierra ·#3D6B4Amusgo ·#7A9A6Ahoja ·#C4B49Asaco ·#B23A2Fficha - Tipo: Spectral 52/36/24 display · Manrope 16/14 UI
Alcance L1 must
- Home: tabla de 5 necesidades (foto, ítem, pedido, comprometido, hueco)
- Fila → compromiso (nombre, email, cantidad) — compromiso ≠ entrega
- Success
CE-MMDD-NNN - Login + inbox STAFF JWT (NEW → RECEIVED / PARTIAL / CANCELLED)
- Empty / loading / error / mobile tabla apilada
- ≥12 UX + ≥10 UI
Supuestos
- S1: Una despensa de barrio cubre 8–20 líneas activas (demo: 5).
- S2: El vecino llega con “qué falta”, no a donar dinero.
- S3: La coordinadora necesita cola con estado, no un CRM de donantes.
- S4: La entrega se confirma en el local; CESTA no cobra.
Hipótesis
| ID | Señal |
|---|---|
| H1 | La tabla reduce “¿qué llevo?” vs lista de WhatsApp |
| H2 | Copy “compromiso, no entrega” baja no-shows de cajas |
| H3 | RECEIVED nunca inventa stock: es acuse, no inventario ERP |
00-paper-reference.md
Paper reference · CESTA
| URL | https://app.paper.design/file/01M04P0VFWGX1DQVSCR5TPWM9Z |
| File ID | 01M04P0VFWGX1DQVSCR5TPWM9Z |
UX-count: 12 UI-count: 11
UX-00 Cover · UX-01 Stakeholders · UX-02 Personas · UX-03 JTBD · UX-04 Stories · UX-05 Journey · UX-06 Blueprint · UX-07 Site map · UX-08 Flujos · UX-09 Datos+permisos · UX-10 Métricas · UX-11 Research UI-00 Tokens · UI-01 Tabla · UI-02 Ficha · UI-03 Compromiso · UI-04 Success · UI-05 Login · UI-06 Inbox · UI-07 Empty · UI-08 Error · UI-09 Loading · UI-10 Mobile tabla
UX process (prefijo UX- · mínimo 12)
| ID | Nombre | Contenido |
|---|---|---|
| UX-00 | Cover | Portada CESTA · eslogan «Lo que falta, en una lista.» · terna R-CL · S-RAIL · H-TABLE · F-LEAD · L1 ONG despensa en especie · mood botanical · fecha 2026-08-16 |
| UX-01 | Stakeholders | VECINO anónimo, STAFF (Inés), despensa de barrio, familias receptoras, donantes en especie — no pasarela de dinero |
| UX-02 | Personas | Rosa Belmonte (54, vecina, trae aceite el sábado) · Inés Marzal (41, coordinadora, inbox antes de abrir) |
| UX-03 | JTBD | Job «qué falta y cuánto puedo traer» + job «saber quién se comprometió antes de abrir el local» |
| UX-04 | Stories | Must: tabla de 5 necesidades, ficha, form comprometer, código CE-, login STAFF, inbox 5 estados, empty/error/loading, mobile tabla apilada |
| UX-05 | Journey | Leer tabla → ficha → compromiso honesto → acuse → contacto STAFF → RECEIVED / PARTIAL |
| UX-06 | Blueprint | Frontstage rail/tabla/form · backstage mostrador del local · sistemas Nest/Neon/JWT |
| UX-07 | Site map | Público table-first + auth STAFF; rail Lista · Inbox · Entrar |
| UX-08 | Flujos | F-LEAD: fila → ficha → POST compromiso → success; F-STAFF inbox + PATCH status |
| UX-09 | Datos+permisos | Need · Request · User STAFF · Session; JWT en GET/PATCH requests |
| UX-10 | Métricas | North star % compromisos CONTACTED antes de la franja de entrega; tap fila; RECEIVED no inventa stock |
| UX-11 | Research | Comprobado / supuesto / hipótesis / decisión — sin entrevistas de campo inventadas |
UI producto (prefijo UI- · mínimo 10)
| ID | Nombre | Flujo |
|---|---|---|
| UI-00 | Tokens | DS botanical: hueso, tierra, musgo, hoja, saco, ficha; Spectral + Manrope |
| UI-01 | Tabla | H-TABLE: tabla densa de necesidades (foto, ítem, pedido, comprometido, hueco) + rail |
| UI-02 | Ficha | Foto real, unidad, pedido / comprometido / hueco, disclaimer compromiso ≠ entrega, CTA |
| UI-03 | Compromiso | Form público; disclaimer compromiso ≠ entrega confirmada |
| UI-04 | Success | Acuse + código CE-MMDD-NNN + próximos pasos |
| UI-05 | Login | Acceso JWT STAFF al inbox |
| UI-06 | Inbox | Lista + estados NEW → CONTACTED → RECEIVED / PARTIAL / CANCELLED |
| UI-07 | Empty | Cero necesidades / inbox vacío |
| UI-08 | Error | Fallo de red / API; reintentar |
| UI-09 | Loading | Skeleton tabla / inbox |
| UI-10 | Mobile tabla | Home tabla ~390px apilada + rail compacto |
Regla: wizard de N pasos → N boards UI solo para esos pasos, además de success/empty/error/loading/staff. CESTA no es wizard: el núcleo es tabla → ficha → form de 1 pantalla.
1. Mapa de canvas (bandas)
| § | Banda | Propósito | Artboards clave |
|---|---|---|---|
| 1 | UX PROCESS | Modelo de servicio de despensa vecinal | UX-00…UX-11 |
| 2 | DESIGN SYSTEM | Tokens botanical + rail + tabla | UI-00 |
| 3 | PUBLIC TABLE | Cara vecina table-first | UI-01, UI-02, UI-03, UI-04, UI-07, UI-10 |
| 4 | STAFF | Flujos autenticados | UI-05, UI-06 |
| 5 | STATES | Resiliencia | UI-08, UI-09 |
Layout canvas (referencia de diseño): origen (0,0) · gaps ~100–120px · UX en 3×4 · UI pública en fila tabla → ficha → compromiso → success · mobile debajo de la tabla. [DECISIÓN] Bandas jerárquicas D-P0-04 / D-P1-04.
2. Mapeo Paper → Angular
| Artboard | Ruta app | Componente |
|---|---|---|
| UI-00 Tokens | — | tokens Tailwind + fuentes |
| UI-01 Tabla | / | HomePage (tabla) |
| UI-02 Ficha | /necesidades/:slug | NeedPage |
| UI-03 Compromiso | /necesidades/:slug/comprometer | CommitPage |
| UI-04 Success | /ok/:code | OkPage |
| UI-05 Login | /login | LoginPage |
| UI-06 Inbox | /inbox | InboxPage |
| UI-07 Empty | / 0 needs · /inbox 0 items | empty en tabla / inbox |
| UI-08 Error | tabla / inbox / form | banner error + retry |
| UI-09 Loading | tabla / inbox | skeletons de filas |
| UI-10 Mobile tabla | / viewport 390 | mismo HomePage + filas apiladas |
[COMPROBADO] Rutas del encargo. No hay /inbox/:id, search, mapa, feed ni hero 2-col.
3. Tokens de diseño en Paper
| Token | Valor | Uso |
|---|---|---|
| Hueso / bg | #E6EFDD | Fondo de página, tabla |
| Tierra | #1C2418 | Texto fuerte, wordmark, rail activo, CTA ink |
| Musgo | #3D6B4A | Acciones positivas, RECEIVED, acento primario |
| Hoja | #7A9A6A | Hueco cubierto, meta positiva, rail hover |
| Saco | #C4B49A | Rails, bordes, cabecera de tabla, skeletons |
| Ficha | #B23A2F | Hueco alto, PARTIAL, urgencia, cancelar |
| Display | Spectral 52/36/24 | Titulares, wordmark, código CE- |
| UI | Manrope 16/14 | Body, labels, form, celdas de tabla |
[COMPROBADO] Hex y tipo salen de docs/00-day-brief.md.
4. Checklist de densidad (anti thin-frames)
| Criterio | UX | DS | Public table | STAFF | States |
|---|---|---|---|---|---|
| Jerarquía tipográfica visible | Sí | Sí | Sí | Sí | Sí |
| Microcopy real (no lorem) | Sí | Sí | Sí | Sí | Sí |
| Tokens botanical aplicados | — | Sí | Sí | Sí | Sí |
| Datos de seed creíbles | Personas | — | 5 necesidades + fotos | Códigos CE- | Empty realista |
| Media / iconografía | Cover | — | Fotos ítem | Badges estado | Skeletons |
| Rail de iconos presente | — | Spec | UI-01, UI-10 | UI-06 | — |
| Tabla densa (no cards feed) | — | Spec | UI-01, UI-10 | — | — |
5. Media
assets/hero.jpg— atmósfera de despensa / cesta / local vecinalassets/needs/arroz.jpg— Arroz redondoassets/needs/aceite.jpg— Aceite de olivaassets/needs/tomate.jpg— Tomate en conservaassets/needs/panales.jpg— Pañales T3assets/needs/leche.jpg— Leche entera
[COMPROBADO] Los seis archivos existen en el case 2026-08-16-cesta/assets/.
6. Enlaces
- Paper: https://app.paper.design/file/01M04P0VFWGX1DQVSCR5TPWM9Z
- Case:
/Users/cristian/orca/ux-projects/2026-08-16-cesta/ - App:
/Users/cristian/orca/cesta-app/ - GitHub: https://github.com/Criscode2022/cesta-app
- Neon project:
jolly-night-99671179 - API:
http://localhost:3016 - Web:
http://localhost:4200
01-project-definition.md
01 — Definición de proyecto — CESTA
1. Identidad
| Campo | Valor |
|---|---|
| Nombre | CESTA |
| Significado | Cesta de la despensa vecinal: lo que falta, visible en una lista, no un llamamiento a donar dinero |
| Eslogan | ”Lo que falta, en una lista.” |
| Una frase | Tabla densa de necesidades en especie + compromiso público + inbox STAFF JWT. |
| Sector | ONG / despensa de barrio (donación en especie, no dinero) |
| Tipo | Web L1 — tabla + compromiso + inbox STAFF — Nivel 1 compacto, craft alto |
| Plataforma | Web responsive (tabla + rail · móvil vecino + desktop staff) |
| Mercado demo | España · despensa de barrio (local demo, no red multi-sede) |
| Idioma | es-ES |
| Fecha caso | 2026-08-16 |
| Terna | R-CL · S-RAIL · H-TABLE · F-LEAD |
[COMPROBADO] Nombre, eslogan, terna, nivel y sector salen de docs/00-day-brief.md.
2. Problema
Principal (hipótesis de diseño)
[HIPÓTESIS] Quien quiere ayudar a la despensa no abre un marketplace ni un Bizum: pregunta “qué falta” en un grupo de WhatsApp, un cartel del local o un mensaje que se pierde. [SUPUESTO] La coordinadora confirma cajas por mensaje suelto, sin cola, sin código y sin ver el hueco (pedido − comprometido) en un solo vistazo.
Secundarios
| Problema | Quién lo sufre | Efecto |
|---|---|---|
| “¿Qué llevo?” enterrado en chats y carteles | VECINO (Rosa) | Trae lo que sobra en casa, no lo que falta |
| Compromiso oral sin acuse | Ambos | Cajas fantasma el sábado; hueco mal contado |
| Mensaje enviado sin código | VECINO | No sabe si llegó; escribe otra vez |
| Excel / WhatsApp sin estado | STAFF (Inés) | No sabe a quién ya contestó ni quién entregó |
| Expectativa de “ya está entregado” | Ambos | Compromiso se cuenta como stock |
Supuestos (no investigación primaria propia)
- S1: Una despensa de barrio cubre 8–20 líneas activas (demo: 5). [SUPUESTO]
- S2: El vecino llega con “qué falta”, no a donar dinero. [SUPUESTO]
- S3: La coordinadora necesita cola con estado, no un CRM de donantes. [SUPUESTO]
- S4: La entrega se confirma en el local; CESTA no cobra. [SUPUESTO]
Hipótesis de producto
| ID | Hipótesis | Señal de validación (futura) |
|---|---|---|
| H1 | La tabla reduce “¿qué llevo?” vs lista de WhatsApp | % sesiones con tap en fila |
| H2 | Copy “compromiso, no entrega” baja no-shows de cajas | ↓ “pensé que ya contaba como entregado” |
| H3 | RECEIVED nunca inventa stock: es acuse, no inventario ERP | RECEIVED no muta requestedQty; hueco = pedido − comprometido |
No se afirman estadísticas de demanda de despensas inventadas. Todo lo anterior es razonamiento de diseño etiquetado.
3. Propuesta de valor
| Para | Valor |
|---|---|
| VECINO (Rosa Belmonte) | Ve qué falta y cuánto hueco queda; se compromete en un minuto; se lleva un código. |
| STAFF (Inés Marzal) | Ve compromisos nuevos, marca contacto antes de abrir, anota recibido / parcial / cancelado sin WhatsApp. |
| Despensa / barrio | Canal mínimo: lista visible + pipeline de cajas, sin TPV ni crowdfunding. |
No es CESTA
| Excluido | Por qué |
|---|---|
| Donación de dinero / Bizum / TPV | S2 + S4: en especie; no cobra |
| Feed / cronología de “historias” | Terna H-TABLE, no H-FEED (eso fue RONDA) |
| Mapa de despensas | Terna no es H-MAP (eso fue DERIVA) |
| Search-first / catálogo facetado | Terna no es H-SEARCH (eso fue PIZARRA) |
| CRM de donantes / socias | S3: cola con estado, no ficha de donante |
| Inventario ERP / almacén | H3: RECEIVED es acuse, no stock |
| Hero 2-col + 3 cards de “cómo donar” | Anti-patrón AP-12; contradice H-TABLE |
4. Objetivos
Negocio / caso de estudio
- Demostrar vertical slice L1 ONG table-first con Need + Request + JWT STAFF.
- Portfolio coherente: Paper (12 UX + 11 UI) + docs + app runnable.
- Terna R-CL · S-RAIL · H-TABLE · F-LEAD frente a RONDA (feed) y DERIVA (mapa).
Usuario
| Rol | Objetivo medible en demo |
|---|---|
| VECINO | Ver el hueco en el primer viewport y enviar compromiso en < 2 min |
| STAFF | Marcar CONTACTED / RECEIVED / PARTIAL en < 3 taps desde el inbox |
No objetivos v1 (explícitos)
- Pagos, Bizum, socio mensual, certificado fiscal
- Cuenta de vecino / “mis compromisos”
- Chat, matching o reputación de donantes
- Mapa de despensas / multi-sede
- Notificación email / WhatsApp transaccional
- Inventario de almacén (entradas/salidas/lote)
- CMS de necesidades para editoras
- Multi-staff con roles editoriales
5. Roles y permisos (resumen)
| Acción | Público (VECINO) | STAFF |
|---|---|---|
| Ver tabla de necesidades | Sí | Sí |
| Ver ficha de necesidad | Sí | Sí |
| POST compromiso | Sí | Sí (mismo form) |
| Login JWT | No (no cuenta vecino) | Sí |
| Listar compromisos | No (401) | Sí |
| Cambiar status | No | Sí |
| Crear / editar Need | No (seed) | No en v1 |
[DECISIÓN] Un solo rol autenticado: STAFF. El vecino es anónimo en captura.
6. Métricas (modelo, no instrumentadas en v1 salvo base)
| Tipo | Métrica | Definición |
|---|---|---|
| North Star | % compromisos válidos CONTACTED antes de la franja de entrega (sábado demo) | Calidad operativa del contacto, no “kg recaudados” |
| Activación | 1.er compromiso público | POST create 2xx |
| Tabla | % sesiones con tap en fila | H1 |
| Expectativa | Reclamaciones “ya estaba entregado” | H2 (cualitativa) |
| Integridad | RECEIVED no muta requestedQty | H3 |
| Salud | Error rate API needs/create/list | 4xx/5xx |
7. Alcance funcional v1 (L1)
| Módulo | Incluido |
|---|---|
| Home tabla | 5 necesidades (foto, ítem, pedido, comprometido, hueco) + rail |
| Ficha | Unidad, cantidades, disclaimer, CTA comprometer |
| Compromiso | Form público + POST /api/requests { needSlug, name, email, quantity, note } |
| Success | Código CE-MMDD-NNN en /ok/:code + próximos pasos |
| Auth | POST /api/auth/login → JWT STAFF |
| Inbox | GET list, PATCH :id { status } |
| Estados UI | Empty, loading skeleton, error de red |
| Mobile tabla | Misma home a ~390px, filas apiladas |
| Seed | 1 STAFF, 5 needs, compromisos de ejemplo |
8. Criterios de aceptación de producto
- Un visitante puede ver la tabla densa sin autenticación y abrir una ficha.
- Un visitante puede enviar un compromiso sin cuenta y recibir un código
CE-…. - La UI dice explícitamente que el compromiso no confirma entrega.
- Sin token, GET/PATCH
/api/requests*responden 401. - GET
/api/needsdevuelve las 5 necesidades seed. - Un STAFF puede iniciar sesión y ver el inbox ordenado (
createdAtdesc). - El inbox permite transicionar
NEW | CONTACTED | RECEIVED | PARTIAL | CANCELLED. - La home es la tabla: no hay hero 2-col + 3 cards como superficie principal.
- Slugs cubiertos en seed:
arroz,aceite,tomate,panales,leche. - Fotos de ítem reales en filas y fichas (assets del case).
- Hueco visible = pedido − comprometido; no se vende como stock ERP.
- Rutas públicas solo las seis del encargo (doc 03).
9. Stack y artefactos
| Capa | Detalle |
|---|---|
| Frontend | Angular + Tailwind · puerto 4200 |
| Backend | NestJS · puerto 3016 |
| DB | Neon PostgreSQL · project jolly-night-99671179 |
| Auth | JWT (STAFF) |
| Diseño | Paper 01M04P0VFWGX1DQVSCR5TPWM9Z |
| Repo app | /Users/cristian/orca/cesta-app/ · GitHub Criscode2022/cesta-app |
[COMPROBADO] Puerto, Neon, Paper file ID, repo y path de app constan en el encargo.
10. Riesgos y mitigaciones
| Riesgo | Impacto | Mitigación v1 |
|---|---|---|
| Spam en form público | Inbox ruidoso | Validación server; honeypot / rate-limit en L1+ |
| Expectativa de entrega instantánea | Cajas fantasma | Copy “compromiso ≠ entrega”; RECEIVED solo STAFF |
| Contar compromiso como stock | Hueco mentiroso | H3: RECEIVED es acuse; no muta pedido |
| Confundir con crowdfunding / Bizum | Expectativa de pago | Copy “en especie”; sin precio ni IBAN |
| PII de vecinos en compromisos | Privacidad | Solo STAFF lista; doc 11 |
| Tabla vacía un martes | Abandono | Empty state “ahora mismo no hay líneas abiertas” |
| Home percibida como landing de ONG genérica | Pérdida de craft | Rail + tabla como superficie; no hero 2-col |
| Confusión “ya doné online” | Expectativa de dinero | No hay checkout; hay compromiso de caja |
11. Glosario
| Término | Definición en CESTA |
|---|---|
| Necesidad | needs: ítem, unidad, pedido, comprometido, hueco |
| Compromiso | requests; no es entrega confirmada |
| Entrega / recibido | Solo cuando STAFF marca RECEIVED |
| Parcial | PARTIAL: llegó menos de lo comprometido |
| Pedido | Cantidad que pide la despensa (requestedQty) |
| Comprometido | Suma de quantity de requests no CANCELLED |
| Hueco | max(0, pedido − comprometido) |
| STAFF | Usuario autenticado que opera el inbox |
| VECINO | Visitante anónimo que se compromete |
| Código CE- | Identificador corto oral (CE-0816-001) |
| Rail | Shell S-RAIL: iconos + panel de tabla |
| En especie | Alimento / higiene / producto; nunca dinero |
12. Decisiones de diseño (cierre de brief)
| ID | Decisión | Alternativa descartada |
|---|---|---|
| D1 | Home = tabla densa (H-TABLE) | Hero 2-col + 3 cards (AP-12) · feed (RONDA) · mapa (DERIVA) · search (PIZARRA) |
| D2 | Shell rail de iconos (S-RAIL) | Top-nav sticky · dock flotante · sidebar CRM · command search |
| D3 | Registro clásico cívico-botánico (R-CL) | Charity cream+naranja (primer instinto, cliché) · chapel · mineral |
| D4 | Flujo F-LEAD (compromiso → cola) | Wizard, kanban, search, mapa, booking |
| D5 | Mood botanical | Warm off-white × terracotta (vetado), chapel, bookish |
| D6 | Un rol autenticado STAFF | Multi-rol vecino / tesorería / admin |
| D7 | Estados RECEIVED / PARTIAL (no SEATED / ENROLLED) | Vocabulario de mesa o municipal |
| D8 | CESTA no cobra | TPV / Bizum / socio / certificado fiscal |
| D9 | Success en /ok/:code | Query ?code= (otros días de la serie) |
| D10 | PATCH /api/requests/:id { status } | Subruta /status (PIZARRA/RONDA) |
13. Relación con el día anterior
| Case | Terna | Por qué CESTA no lo copia |
|---|---|---|
| RONDA (N−1) | S-DOCK / H-FEED / F-BOOK | No gaming, no cronología, no dock, no sillas |
| DERIVA (N−2) | S-SPLIT / H-MAP / F-MAP | No turismo, no plano, no split mapa |
[COMPROBADO] El brief exige 0 códigos iguales a N−1 y ≥2 distintos vs N−2. Terna de hoy: R-CL · S-RAIL · H-TABLE · F-LEAD. Ventana 4 días ya tenía R-HY e R-IN; hoy R-CL.
02-ux-research-strategy.md
02 — Estrategia de investigación UX — CESTA
Importante: este documento contiene artefactos de diseño y razonamiento secundario.
No hay entrevistas de campo primarias ni estadísticas inventadas presentadas como dato medido.
Etiquetas: [COMPROBADO] en producto, [SUPUESTO], [HIPÓTESIS], [DECISIÓN].
1. Objetivos de investigación (del caso)
| Objetivo | Método en este caso | Salida |
|---|---|---|
| Entender actores de la despensa vecinal | Modelado de stakeholders + personas | §3–4 |
| Definir job de “qué falta” y de hueco | JTBD + stories Must | §5 |
| Mapear fricción chat → caja | Journey + service blueprint | §6–7 |
| Traducir a requisitos L1 table-first | Matriz hallazgo → requisito → feature | §8 |
2. Fuentes y límites
Fuentes admisibles (secundarias / operativas)
- Conocimiento general de despensas de barrio, bancos de alimentos de proximidad y grupos de WhatsApp de donación en especie.
- Analogía operativa con tablas densas + form público + inbox JWT de la serie daily.
- Restricciones ALS-2: no feed, no mapa, no search-first, no pagos, no CRM de donantes.
Límites éticos de verdad
| Prohibido | Permitido |
|---|---|
| “El 64% de vecinos abandona el grupo de WhatsApp” sin fuente | “[SUPUESTO] el vecino llega con ‘qué falta’” |
| Citas de entrevistas ficticias como campo real | Quotes de persona etiquetadas como constructo de diseño |
| SLA medido “contacto en 2 h el viernes” como KPI real | North star modelo “CONTACTED antes de la franja de entrega” |
| Stock real de una despensa concreta | Pedidos de seed de producto |
| “Inés existe y nos dijo…” | Inés Marzal es persona de diseño |
3. Stakeholders
| Stakeholder | Influencia | Interés | Necesidad principal |
|---|---|---|---|
| VECINO / vecina que trae caja | Baja formal | Muy alta | Ver qué falta y dejar compromiso con acuse |
| STAFF / coordinadora (Inés) | Alta | Muy alta | Cola con estado y código oral antes de abrir |
| Familias receptoras | Alta misión | Alta | Que llegue lo que falta, no lo que sobra |
| Voluntariado de mostrador | Media | Alta | Lista del sábado sin cajas fantasma |
| Junta / entidad anfitriona | Alta adopción | Media | Canal mínimo, sin TPV ni campaña de dinero |
| Banco de alimentos / gran ONG | Baja v1 | Baja | Fuera: CESTA es despensa de barrio, no red logística |
| WhatsApp / cartel del local | Competencia / canal paralelo | — | No clonar el chat; sí sustituir la lista informal |
Mapa de poder (resumen)
- Decisor de adopción: coordinadora de la despensa (quien abre el local).
- Usuario frecuente de captura: VECINO (tabla + form) y STAFF (inbox del día).
- Riesgo de rechazo: si la home no enseña el hueco en el primer viewport, o si el form implica “ya está entregado”.
4. Personas
P1 — Rosa Belmonte · VECINO
| Campo | Detalle |
|---|---|
| Edad / contexto | 54 años; vecina del barrio; el sábado puede pasar por el súper y dejar aceite |
| Digital | Media; móvil grande; usa WhatsApp; evita “portales de donación” con certificado |
| Goals | Ver qué falta de verdad, comprometer una cantidad concreta, guardar un código |
| Pains | Listas de chat desactualizadas, no saber si “traigo aceite” ya cuenta, llevar lo que sobra |
| Quote de diseño | “Dime qué falta y cuántos litros. El sábado paso yo.” |
| Seed demo | rosa.belmonte@example.com · compromiso aceite CE-0816-001 |
Escenario: Abre CESTA, ve Aceite de oliva con hueco, entra a la ficha, compromete 3 L, guarda CE-0816-001.
[DECISIÓN] Rosa no tiene cuenta. Crear usuario de vecina sería fricción y no aporta al job de captura.
P2 — Inés Marzal · STAFF
| Campo | Detalle |
|---|---|
| Edad / contexto | 41 años; coordinadora de la despensa; mira el inbox antes de abrir el local |
| Digital | Media–alta; desktop para la cola; móvil para devolver llamadas |
| Goals | Ver NEW del día, contactar, marcar RECEIVED o PARTIAL en mostrador sin Excel |
| Pains | Mensajes sueltos, no sabe quién ya fue avisado, compromiso contado como stock |
| Quote de diseño | “Si está en NEW, es mía. Si está RECEIVED, la caja está aquí. Si no, no invento kilos.” |
| Email demo | ines@cesta.barrio / password123 |
Escenario: Login antes de abrir → inbox → Rosa NEW → escribe / llama → CONTACTED → el sábado RECEIVED (o PARTIAL si trajo menos).
[COMPROBADO] Edad, rol y credencial salen del encargo / brief.
Anti-personas
| Quién | Por qué no es target v1 |
|---|---|
| Donante que quiere certificado fiscal / Bizum | Eso es tesorería, no despensa en especie |
| Logística de banco de alimentos regional | Otro producto (almacén, rutas, lotes) |
| Familia receptora que pide cesta | CESTA no es mostrador de reparto a familias |
| Community manager que quiere feed de “historias de impacto” | Rompe H-TABLE |
5. JTBD y user stories
Job principal (VECINO)
Cuando quiero ayudar a la despensa del barrio,
quiero ver qué falta y dejar una cantidad con mi nombre,
para que coordinación me contacte sin perder el mensaje y sin creer que ya entregué.
Job principal (STAFF)
Cuando se acercan el día de entrega y empiezan a llegar compromisos,
quiero verlos con ítem, cantidad y estado,
para contactar antes de abrir y anotar recibido o parcial sin inventar stock.
Jobs secundarios
| Job | Rol |
|---|---|
| Distinguir pedido, comprometido y hueco | VECINO |
| Referir un compromiso por código corto en el mostrador | Ambos |
| Ver cuántas NEW hay antes de abrir | STAFF |
| Marcar entrega parcial si llegó menos | STAFF |
| Cancelar duplicado o no-show | STAFF |
Stories Must (v1)
| ID | Story | AC |
|---|---|---|
| US1 | Como vecina, quiero ver las necesidades en una tabla | GET /api/needs; columnas foto, ítem, pedido, comprometido, hueco |
| US2 | Como vecina, quiero abrir la ficha con unidad y hueco | GET :slug; foto real; CTA |
| US3 | Como vecina, quiero enviar compromiso sin cuenta | POST 201 + redirect /ok/:code |
| US4 | Como vecina, quiero un código y saber que no he entregado todavía | code en /ok/:code + copy disclaimer |
| US5 | Como STAFF, quiero entrar con email/password | JWT + redirect /inbox |
| US6 | Como STAFF, quiero listar compromisos | GET list JWT |
| US7 | Como STAFF, quiero cambiar estado incl. RECEIVED y PARTIAL | PATCH { status } |
| US8 | Como cualquiera, quiero ver empty / error / loading | UI-07, UI-08, UI-09 |
| US9 | Como vecina en móvil, quiero la tabla usable a ~390px | UI-10; filas apiladas; targets ≥44px |
MoSCoW (v1 L1)
| Prioridad | Ítems |
|---|---|
| Must | Tabla home, ficha, form, success, login, inbox, 5 estados, empty/loading/error, mobile tabla + rail |
| Should | Hueco visible en cada fila; labels ES de enums; rail compacto en 390 |
| Could | Copiar código al portapapeles; click-to-call; persistir scroll de tabla |
| Won’t | Pagos, cuenta vecino, mapa, search, email transaccional, inventario ERP |
6. Journey (VECINO → STAFF)
| Fase | Actor | Acción | Touchpoint | Emoción [HIPÓTESIS] |
|---|---|---|---|---|
| 1 Mirar | Rosa | Abre CESTA; ve la tabla | Home tabla + rail | Claridad, baja fricción |
| 2 Elegir | Rosa | Toca Aceite de oliva 24 L | Fila → ficha | Control |
| 3 Entender | Rosa | Lee unidad L, hueco, “el sábado” | /necesidades/aceite | Confianza o duda de hueco |
| 4 Comprometer | Rosa | Form + disclaimer | /necesidades/aceite/comprometer | Prudencia |
| 5 Acuse | Rosa | Ve CE-0816-001 | /ok/CE-0816-001 | Alivio (no euforia de “ya entregué”) |
| 6 Contacto | Inés | Ve NEW, escribe | Inbox | Control operativo |
| 7 Cierre | Inés | RECEIVED o PARTIAL | PATCH | Cierre honesto |
Momentos de verdad
- Tabla en el primer viewport — si hay que “descubrir la marca”, se rompe S2. [HIPÓTESIS]
- Disclaimer en form y success — si falta, H2 falla.
- Código CE- — prueba de “ha llegado” para el mostrador y el mensaje.
- RECEIVED / PARTIAL visibles — evita la mentira social del “ya cuenta como stock”.
- Rail no esconde Inbox — Inés antes de abrir no busca un menú hamburger. [HIPÓTESIS]
7. Service blueprint (resumen)
| Capa | Elementos |
|---|---|
| Frontstage VECINO | Rail, tabla, ficha, form, success |
| Frontstage STAFF | Login, inbox, acciones de estado |
| Backstage | Mensaje / llamada / caja física en el local (fuera de app) |
| Sistemas | Nest API :3016, Neon jolly-night-99671179, JWT, Angular tabla |
| Soportes | Seed 5 necesidades + fotos, Paper, docs |
| Fallos | 0 líneas; 401 sin token; 404 slug; red caída → UI error |
Fallos de servicio y respuesta de diseño
| Fallo | Evidencia de UI | Recuperación |
|---|---|---|
| Sin líneas abiertas | UI-07 Empty tabla | “Ahora mismo no hay líneas abiertas.” |
| Inbox vacío | UI-07 Empty inbox | “Cuando alguien se comprometa, aparece aquí.” |
| API caída | UI-08 Error | Reintentar |
| Latencia tabla | UI-09 Loading | Skeleton de 5 filas |
| Form inválido | Inline field | No navegar a success |
| Trajo menos (ops) | STAFF marca PARTIAL | No se inventa el kilo que no llegó [H3] |
8. Matriz hallazgo → requisito → feature
| Hallazgo | Tipo | Requisito | Feature v1 |
|---|---|---|---|
| Llega con “qué falta” | [SUPUESTO] S2 | Tabla es la home | UI-01 / H-TABLE |
| Chat no muestra hueco | [HIPÓTESIS] H1 | Columnas pedido / comprometido / hueco | GET needs con agregados |
| Sin acuse | [HIPÓTESIS] | Código oral | Success + CE- |
| Confunden compromiso con entrega | [HIPÓTESIS] H2 | Copy irrenunciable | Disclaimer form/success |
| Compromiso contado como stock | [HIPÓTESIS] H3 | RECEIVED es acuse | Enum + no mutar pedido |
| STAFF opera antes de abrir | [SUPUESTO] | Rail + inbox denso | S-RAIL / UI-06 |
| PII de vecinos | [DECISIÓN] | Auth JWT STAFF | Login + guards API |
| L1 compacto, no CRM | [DECISIÓN] | Un rol STAFF; needs seed | Sin cuenta VECINO, sin CMS |
9. Preguntas abiertas (no bloquean v1)
| ID | Pregunta | Cómo se resolvería después |
|---|---|---|
| Q1 | ¿Una cola por despensa o varias sedes? | venueId + scope de User L2+ |
| Q2 | ¿Email automático “hemos recibido tu compromiso”? | Hook post-create; copy ya promete contacto |
| Q3 | ¿Checkbox RGPD + política? | Legal L1+; minimización ya aplicada |
| Q4 | ¿El hueco a 0 bloquea el POST? | [DECISIÓN v1] no; STAFF decide; copy avisa |
| Q5 | ¿PARTIAL ajusta quantity o solo el badge? | Badge + nota; ajuste numérico = L1+ |
| Q6 | ¿Franja de entrega es solo sábado? | [SUPUESTO de demo] sábado 10–13; no es horario oficial de un local real |
10. Plan de research futuro (si hubiera campo real)
| Método | Muestra orientativa | Pregunta |
|---|---|---|
| Test de usabilidad tabla | 5–6 vecinas 40–70 | ¿Leen el hueco en < 20 s? |
| Shadowing STAFF 1 sábado | 1 despensa | ¿El inbox sustituye el WhatsApp del mostrador? |
| Revisión de no-shows | Compromisos RECEIVED vs NEW | Validar H2 (cajas fantasma) |
| Card sort de unidades | 6–8 participantes | ¿kg / L / ud / packs se entienden? |
Estos métodos no se han ejecutado. No se reportan hallazgos como si lo hubieran sido.
11. Síntesis
CESTA se diseña como lista de lo que falta con cola de compromiso, no como crowdfunding, no como inventario y no como feed de impacto.
La investigación del caso es constructiva y etiquetada: personas, JTBD y journey son artefactos de diseño; las métricas de validación quedan para uso real futuro.
03-information-architecture.md
03 — Arquitectura de información — CESTA
1. Principios de IA
| Principio | Aplicación |
|---|---|
| Table first | La home es la tabla de necesidades. No hay landing de marca por delante. |
| Fila → ficha → compromiso | Profundidad 2 desde la tabla hasta el form |
| Público vs STAFF | Tabla y form abiertos; inbox solo autenticado |
| Lenguaje de dominio | Necesidad, hueco, compromiso, entrega — no “SKU”, “lead CRM”, “donación online” |
| Código visible | CE-… en success e inbox para referencia oral / mostrador |
| Honestidad de estado | Compromiso ≠ entrega; RECEIVED / PARTIAL son ciudadanos de primera |
| Rail siempre a mano | S-RAIL: iconos + panel; no mega-menú |
2. Sitemap
/ Tabla de necesidades (público) H-TABLE
/necesidades/:slug Ficha de necesidad (público)
/necesidades/:slug/comprometer Formulario de compromiso (público)
/ok/:code Success post-compromiso (público)
/login Login JWT STAFF
/inbox Lista de compromisos (auth STAFF)
/** → redirect /
Árbol por audiencia
| Audiencia | Nodos relevantes |
|---|---|
| VECINO | Tabla → Ficha → Comprometer → Ok |
| STAFF | Login → Inbox (y tabla pública si consulta hueco) |
| Ambos | Wordmark → / (STAFF autenticada: rail Inbox) |
[COMPROBADO] Seis rutas del encargo. No se inventan /inbox/:id, /buscar, /mapa, /donar.
3. Navegación
Rail de iconos (S-RAIL)
| Slot | Destino | Visibilidad | Notas |
|---|---|---|---|
| Lista | / | Todos | Icono tabla; estado activo en home |
| Inbox | /inbox | STAFF; si guest → /login?next=/inbox | Cola operativa |
| Entrar | /login si guest; acción salir si auth | Todos | Acceso STAFF |
[DECISIÓN] El rail es la única navegación primaria. Tres destinos: encaja L1 compacto. No hay top-nav de 7 ítems, no dock flotante (RONDA), no command search (PIZARRA).
Desktop ≥960px: rail vertical izquierdo (~56–64px) + panel de tabla.
Mobile ~390px: rail compacto inferior o superior de iconos; la tabla se apila (UI-10). [DECISIÓN] En 390 el rail no compite con un hamburger.
Pública (además del rail)
| Elemento | Destino | Notas |
|---|---|---|
| Wordmark CESTA | / | Spectral; no “fundación + hero” |
| Fila de necesidad | /necesidades/:slug | Toda la fila es el enlace |
| Comprometer | /necesidades/:slug/comprometer | Primary en ficha |
| Entrada STAFF discreta | /login | También vía rail |
STAFF (autenticado)
| Elemento | Destino | Visibilidad |
|---|---|---|
| Inbox | /inbox | STAFF |
| Badge rol | — | STAFF |
| Nombre | Inés Marzal | STAFF |
| Salir | limpia token → /login | STAFF |
| Acciones de estado | en la propia lista /inbox | STAFF · no hay ruta detalle |
[DECISIÓN] L1: el PATCH vive en la fila del inbox. No se añade /inbox/:id.
4. Inventario de contenido
| Pantalla | Contenidos |
|---|---|
Tabla / | Wordmark, eslogan, tabla (foto, ítem, pedido, comprometido, hueco), empty, loading, error, rail |
| Ficha | Hero foto, título, unidad, pedido / comprometido / hueco, nota de entrega, disclaimer, CTA |
| Comprometer | Título ítem, disclaimer, campos (nombre, email, cantidad, nota), submit |
| Success | Mensaje, código CE-, “no es entrega”, siguiente paso (sábado demo) |
| Login | Título panel despensa, email, password, submit, error |
| Inbox | Lista densa: código, nombre, ítem, cantidad, status, acciones, empty, error |
5. Taxonomía
RequestStatus
| Status API | Label UI | Semántica |
|---|---|---|
NEW | Nuevo | Acaba de llegar; sin contacto |
CONTACTED | Contactado | STAFF inició llamada / mensaje |
RECEIVED | Recibido | La caja está en el local (acuse, no stock ERP) |
PARTIAL | Parcial | Llegó menos de lo comprometido |
CANCELLED | Cancelado | No-show, duplicado, desiste |
Orden de inbox: createdAt descendente (más reciente primero).
[DECISIÓN] No hay WAITLIST: el hueco es numérico, no una silla.
NeedUnit
| API | Label UI | Seed |
|---|---|---|
kg | kg | arroz |
L | L | aceite, leche |
ud | ud | tomate |
packs | packs | pañales |
Need (líneas seed)
| slug | Título | Pedido | Unidad |
|---|---|---|---|
arroz | Arroz redondo | 40 | kg |
aceite | Aceite de oliva | 24 | L |
tomate | Tomate en conserva | 36 | ud |
panales | Pañales T3 | 12 | packs |
leche | Leche entera | 48 | L |
[COMPROBADO] Slugs y cantidades salen del encargo.
6. Modelo mental vs UI
| Modelo mental | Representación |
|---|---|
| “¿Qué falta?” | Tabla home, columna hueco |
| “¿Cuánto aceite?” | Ficha: pedido / comprometido / hueco + unidad L |
| “Traigo el sábado” | Form /comprometer + disclaimer |
| “Me dieron un número” | code CE- |
| “Lista del día de gente nueva” | Inbox + badge NEW |
| “Ya le llamé” | CONTACTED |
| “La caja está aquí” | RECEIVED |
| “Trajo menos” | PARTIAL |
| “No vino” | CANCELLED |
7. Query string
L1 table-first no usa facetas de catálogo.
Único query admitido: /login?next=/inbox (redirect post-login).
[DECISIÓN] Sin ?q= ni chips. Eso sería H-SEARCH (PIZARRA).
8. Rutas API alineadas a IA
| UI | API |
|---|---|
| Tabla | GET /api/needs |
| Ficha | GET /api/needs/:slug |
| Submit compromiso | POST /api/requests { needSlug, name, email, quantity, note } |
| Login | POST /api/auth/login |
| Inbox list | GET /api/requests JWT |
| Cambiar estado | PATCH /api/requests/:id { status } |
[COMPROBADO] Contrato del encargo. No se documentan /api/requests/stats ni POST /api/needs como Must.
9. Decisiones de IA descartadas
| Idea | Por qué no en L1 v1 |
|---|---|
/explorar editorial + tabla secundaria | Rompe H-TABLE |
| Área “mis compromisos” por email mágico | Cuenta de facto; authz delicada |
/inbox/:id | L1 compacto; acciones en lista |
| Search / chips de categoría | H-SEARCH de PIZARRA |
| Mapa de despensas | H-MAP de DERIVA |
| Feed de entregas | H-FEED de RONDA |
| Wizard de 4 pasos de donación | F-ONB; el form es 1 pantalla |
/donar con importe | Rompe S2 / S4 |
04-user-flows.md
04 — Flujos de usuario — CESTA
Convenciones
- Actor: Guest (VECINO) | STAFF
- Éxito: resultado observable
- Errores: UI + código HTTP cuando aplica
- Flujo canónico: F-LEAD (compromiso público → cola staff)
F1 — Descubrimiento por tabla (VECINO)
/ → tabla de necesidades (H-TABLE)
→ GET /api/needs
→ filas densas | empty | error | skeleton
→ click fila → /necesidades/:slug
→ rail Inbox / Entrar → /login (si guest)
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Aterriza; la primera fila (foto + hueco) es el primer foco | Render home |
| 2 | Recorre ítem, pedido, comprometido, hueco | GET needs |
| 3 | Abre ficha | Router |
| 4 | Rail permanece visible | Shell S-RAIL |
Éxito: al menos una ficha alcanzable, o empty accionable.
AC: no hay que hacer scroll de “marca” para llegar a la primera fila.
Errores / estados
| Caso | Comportamiento |
|---|---|
| 0 necesidades | UI-07: “Ahora mismo no hay líneas abiertas.” |
| Red / 5xx | UI-08 + Reintentar |
| Primera carga | UI-09 skeleton de 5 filas |
F2 — Entender necesidad (VECINO)
/necesidades/:slug → GET /api/needs/:slug
→ foto, unidad, pedido, comprometido, hueco, disclaimer
→ CTA "Comprometer" → /necesidades/:slug/comprometer
→ 404 slug → mensaje + volver a la tabla
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee qué falta y en qué unidad | Need |
| 2 | Contrasta hueco | requestedQty − committedQty |
| 3 | Decide comprometer o volver a la tabla | Router |
Éxito: CTA visible; disclaimer “comprometer no confirma entrega” visible antes del form.
Error: 404 si slug inexistente.
AC ficha
| # | Criterio |
|---|---|
| 1 | Foto real del imageKey (no color sólido) |
| 2 | Título + unidad (kg / L / ud / packs) |
| 3 | Pedido, comprometido y hueco visibles |
| 4 | Disclaimer visible sobre el fold en desktop |
| 5 | CTA “Comprometer” (aunque hueco = 0: copy avisa) |
[DECISIÓN] Hueco 0 no oculta el CTA: el vecino puede comprometer de más; STAFF decide. Copy: “El hueco puede haberse cubierto; te contactamos igual.”
F3 — Comprometer (VECINO, público)
/necesidades/:slug/comprometer
→ validación cliente
→ POST /api/requests {
needSlug,
name,
email,
quantity,
note
}
→ 201 Request { code, status: NEW, ... }
→ /ok/:code
| Campo | Validación cliente (mín.) | API |
|---|---|---|
| needSlug | required (de la ruta) | existe en DB |
| name | required, min 2 | @MinLength(2) |
| required, email | @IsEmail() | |
| quantity | required, number > 0 | @IsNumber() @Min(0.01) entero si unidad ud/packs |
| note | optional | default "" / @MaxLength(400) |
Éxito: registro status=NEW, código CE-MMDD-NNN, pantalla success.
No hay cuenta de vecino en v1.
Errores
| Caso | Comportamiento |
|---|---|
| Validación DTO | 400 + mensaje de campo |
| needSlug inexistente | 400 / 404 |
| Red caída | Error de red en form; no navegar a /ok/… |
| Email mal formado | 400 |
| Doble submit | Botón disabled mientras pending |
| quantity ≤ 0 | 400 |
[DECISIÓN] No se bloquea el POST aunque hueco sea 0. Bloquear en cliente mentiría sobre el mostrador (Q4).
[COMPROBADO] Body del POST = { needSlug, name, email, quantity, note } según encargo.
F4 — Success (VECINO)
/ok/:code → copy de acuse + code
→ “Esto no confirma la entrega”
→ CTA volver a la tabla / ir a la ficha
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee acuse y código (param de ruta) | UI |
| 2 | Conserva el código (foto, papel, nota) | Fuera de app |
| 3 | Espera llamada / mensaje de coordinación | Fuera de app |
AC: el código es seleccionable; el disclaimer es visible sin scroll en desktop.
Si se entra a /ok/algo-que-no-existe sin haber POST: se muestra el código de la URL + copy genérico; no se inventa un registro. [DECISIÓN] Success es acuse de UI, no lookup público (lookup = L2+).
F5 — Login JWT (STAFF)
/login → POST /api/auth/login { email, password }
→ 200 { accessToken, user } → localStorage → /inbox (o ?next sanitizado)
→ 401 → mensaje error en form
| Campo | Validación cliente | API |
|---|---|---|
| required, email | @IsEmail() | |
| password | required, min 6 | @MinLength(6) |
Éxito: token guardado; user role=STAFF, nombre Inés Marzal.
Credencial demo: ines@cesta.barrio / password123.
| Caso | Comportamiento |
|---|---|
| Credenciales inválidas | 401 + mensaje |
| Red caída | Error de red en UI |
| Token caducado en inbox | 401 en GET → re-login |
F6 — Inbox (STAFF)
/inbox (token en cliente)
→ GET /api/requests
→ Render filas + badges + acciones
| Caso | Comportamiento |
|---|---|
| Lista con items | Filas: code, nombre, ítem, quantity + unidad, status, createdAt |
| Lista vacía | Empty: “Nadie se ha comprometido todavía.” |
| Fallo red / 401 | Error + reintentar / re-login |
[DECISIÓN] Sin stats endpoint en v1 (no está en el contrato API). Recuentos, si se muestran, se derivan del array.
F7 — Cambio de estado (STAFF, en la lista)
/inbox
→ PATCH /api/requests/:id { status }
→ 200 Request actualizado
Transiciones típicas (máquina simple L1)
| Desde | Hacia | Intención |
|---|---|---|
| NEW | CONTACTED | Inés inició contacto |
| CONTACTED | RECEIVED | La caja está en el local |
| CONTACTED | PARTIAL | Llegó menos de lo comprometido |
| NEW | RECEIVED | Llegó sin aviso previo (mostrador) |
| * | CANCELLED | No-show, duplicado, desiste |
| PARTIAL | RECEIVED | Completó después |
| RECEIVED | CANCELLED | Corrección (devolución / error) |
| * | * | Corrección operativa (API acepta enum) |
Éxito: badge actualizado en la fila.
Errores: 404 id; 401 sin token; 400 status inválido.
Regla de integridad H3 (ops, no trigger DB v1): RECEIVED / PARTIAL no mutan requestedQty del Need. El hueco se calcula por suma de quantity no CANCELLED. Ajuste fino de kilos = L1+.
[COMPROBADO] PATCH body = { status } sobre /api/requests/:id.
F8 — Logout (STAFF)
Click "Salir" (rail) → limpia localStorage (token + user) → /login
Matriz de errores global
| Código | Cuándo | UI |
|---|---|---|
| 400 | DTO inválido | Mensaje campo / genérico |
| 401 | Sin/mal token | Re-login |
| 404 | id o slug no existe | Mensaje + volver |
| 5xx / network | API caída | Error + retry |
Flujos fuera de alcance v1
- Registro / recuperación de password de STAFF
- “Mis compromisos” por código público
- Notificación email automática
- Filtros multi-criterio en inbox
- Detalle
/inbox/:id - Pago / Bizum
- Alta de Need por UI
05-data-model.md
05 — Modelo de datos — CESTA
1. Visión general
Dominio L1 de tabla de necesidades + cola de compromiso:
| Entidad | Tabla SQL | Propósito |
|---|---|---|
| User | users | Identidad de coordinación (rol STAFF) |
| Session | sessions | Sesión JWT / token persistido (Should) |
| Need | needs | Línea de la despensa (oferta de lo que falta) |
| Request | requests | Compromiso ligado a una necesidad |
Base: PostgreSQL (Neon project jolly-night-99671179) · IDs: cuid() o entero serial.
[DECISIÓN] ensureSchema o Prisma son válidos si el contrato de tablas se cumple; el brief de stack no exige Prisma.
2. Enums (aplicación; persistidos como TEXT + check)
Role
| Valor | Descripción |
|---|---|
STAFF | Operadora del inbox; único rol autenticado v1 |
RequestStatus
| Valor | Descripción |
|---|---|
NEW | Recién creado por el form público |
CONTACTED | STAFF ha iniciado contacto |
RECEIVED | Entrega confirmada en el local (acuse, no stock ERP) |
PARTIAL | Llegó menos de lo comprometido |
CANCELLED | Anulado |
NeedUnit
kg | L | ud | packs
3. Diagrama ER (texto)
users
id, email, password_hash, name, role(STAFF)
created_at, updated_at
1 ──< sessions
sessions
id, user_id → users
token_hash, expires_at
created_at
needs
id, slug (unique), title, summary, description
unit, requested_qty, image_key
created_at, updated_at
1 ──< requests
requests
id, code (unique)
need_id → needs -- o need_slug
name, email
quantity
note
status (default NEW)
created_at, updated_at
No hay FK entre
requestsyusersen v1: el form es anónimo. STAFF opera sobre el conjunto global.
4. Tablas
users
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| TEXT | UNIQUE NOT NULL | |
| password_hash | TEXT | NOT NULL (bcrypt) |
| name | TEXT | NOT NULL |
| role | TEXT | NOT NULL DEFAULT 'STAFF' |
| created_at | TIMESTAMPTZ | DEFAULT now() |
| updated_at | TIMESTAMPTZ | DEFAULT now() |
sessions
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| user_id | TEXT | NOT NULL FK → users(id) ON DELETE CASCADE |
| token_hash | TEXT | NOT NULL |
| expires_at | TIMESTAMPTZ | NOT NULL |
| created_at | TIMESTAMPTZ | DEFAULT now() |
[DECISIÓN] El JWT viaja en Authorization: Bearer. sessions permite invalidar (logout) sin esperar expiración. Si el slice usa JWT stateless, documentar el hueco; D-P1-05 se cumple igual con guard.
needs
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| slug | TEXT | UNIQUE NOT NULL, kebab-case |
| title | TEXT | NOT NULL |
| summary | TEXT | card / fila (~140) |
| description | TEXT | ficha; default '' |
| unit | TEXT | NOT NULL (kg | L | ud | packs) |
| requested_qty | NUMERIC | > 0 · pedido de la despensa |
| image_key | TEXT | filename (arroz.jpg …) |
| created_at / updated_at | TIMESTAMPTZ |
committed_qty y gap no se persisten: se calculan. [DECISIÓN]
requests
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT o INT | PK |
| code | TEXT | UNIQUE, formato CE-MMDD-NNN |
| need_id | TEXT | FK Need (o need_slug) |
| name | TEXT | NOT NULL |
| TEXT | NOT NULL | |
| quantity | NUMERIC | > 0 |
| note | TEXT | default '' |
| status | TEXT | default NEW |
| created_at / updated_at | TIMESTAMPTZ |
5. Reglas de integridad y negocio
| Regla | Implementación |
|---|---|
| Create público | POST /api/requests sin guard |
| List / patch | JWT → 401 sin token |
| Catálogo público | GET /api/needs y :slug sin guard |
| Código único | CE-MMDD- + secuencia 001–999 del día [DECISIÓN] Europe/Madrid (fallback +1 si colisión) |
| Status default | NEW al crear |
| Password | Nunca en claro; solo password_hash |
| Note | Coalesce a "" si omitida |
| Orden listado requests | created_at desc |
| Orden tabla needs | estable: seed order o title asc (no “destacados”) |
| Comprometido | SUM(quantity) WHERE status <> 'CANCELLED' |
| Hueco | GREATEST(0, requested_qty − comprometido) |
| RECEIVED vs pedido | No muta requested_qty (H3) |
| POST con hueco 0 | Permitido (Q4) |
6. Contratos API (resumen)
GET /api/needs (público)
Response 200: Need[] con campos de fila + agregados:
{
"slug": "aceite",
"title": "Aceite de oliva",
"unit": "L",
"requestedQty": 24,
"committedQty": 3,
"gap": 21,
"imageKey": "aceite.jpg",
"summary": "…"
}
JSON camelCase hacia el web. [DECISIÓN]
GET /api/needs/:slug (público)
Need o 404. Incluye description.
POST /api/requests (público)
Body
| Campo | Tipo | Req |
|---|---|---|
| needSlug | string | sí |
| name | string | sí |
| string | sí | |
| quantity | number | sí |
| note | string | no |
Response 201: Request + need embebido mínimo (title, slug, unit) + code + status: "NEW".
[COMPROBADO] Campos del body = encargo.
POST /api/auth/login
Body: { email, password }
Response 200: { accessToken, user: { id, email, name, role } }
GET /api/requests (JWT)
Array Request + need, createdAt desc. Sin token → 401.
PATCH /api/requests/:id (JWT)
Body: { status: RequestStatus }
Response: Request actualizado. Sin token → 401.
[COMPROBADO] PATCH sobre :id con { status }, no /status.
7. Seed de referencia (2026-08-16)
STAFF
| Nombre | Password | |
|---|---|---|
| Inés Marzal | ines@cesta.barrio | password123 |
[COMPROBADO] Credencial del encargo.
Needs
| slug | Título | Unidad | Pedido | Asset |
|---|---|---|---|---|
| arroz | Arroz redondo | kg | 40 | arroz.jpg |
| aceite | Aceite de oliva | L | 24 | aceite.jpg |
| tomate | Tomate en conserva | ud | 36 | tomate.jpg |
| panales | Pañales T3 | packs | 12 | panales.jpg |
| leche | Leche entera | L | 48 | leche.jpg |
[COMPROBADO] Cinco líneas del encargo.
Textos de summary / description son contenido de producto demo, no inventario de una despensa real. [SUPUESTO de catálogo]
| slug | Summary demo |
|---|---|
| arroz | Base de la cesta semanal. Preferible redondo, paquete cerrado. |
| aceite | Para cocinar. Garrafa o botellas; anotar litros. |
| tomate | Latas o botes. Cuenta por unidad. |
| panales | Talla 3. Pack cerrado. |
| leche | Entera, brick. Anotar litros. |
Requests
| Código | Nombre | Need | Qty | Estado | Nota demo |
|---|---|---|---|---|---|
| CE-0816-001 | Rosa Belmonte | aceite | 3 | NEW | “Paso el sábado por la mañana.” |
| CE-0816-002 | Luis Aranda | arroz | 5 | CONTACTED | — |
| CE-0816-003 | Marta Gil | leche | 6 | RECEIVED | Entregado viernes |
| CE-0816-004 | Youssef Amrani | tomate | 8 | PARTIAL | Trajo 4; queda pendiente |
| CE-0816-005 | Pilar Soto | panales | 2 | CANCELLED | No puede este sábado |
El sufijo de fecha del código de seed es el día del caso; la generación runtime usa la fecha Europe/Madrid del servidor.
8. Evolución posible (no implementada)
| Cambio | Nivel |
|---|---|
venueId + scope de STAFF | L2 |
Ajuste numérico de quantity en PARTIAL | L1+ |
| Check DB / transacción de hueco | L2 |
| CMS Need autenticado | L2 / L3 |
| User VECINO con historial | L3 |
| Soft delete + audit log de status | L2 |
06-tech-stack.md
06 — Stack tecnológico — CESTA
1. Visión
| Capa | Tecnología | Notas |
|---|---|---|
| Frontend | Angular standalone + signals | Puerto 4200 |
| Estilos | Tailwind CSS | Tokens botanical (hueso/tierra/musgo/hoja/saco/ficha) |
| Backend | NestJS TypeScript | Puerto 3016, prefijo /api |
| Persistencia | PostgreSQL (ensureSchema o Prisma) | Tablas users, sessions?, needs, requests |
| DB | Neon PostgreSQL | project jolly-night-99671179 |
| Auth | JWT | Rol STAFF |
| Diseño | Paper | 01M04P0VFWGX1DQVSCR5TPWM9Z |
| Repo | /Users/cristian/orca/cesta-app/ | GitHub Criscode2022/cesta-app |
Stack fijo del cron (D-P0-03). No React/Next/Firebase.
[COMPROBADO] Puerto 3016, Neon, Paper y repo constan en el encargo.
2. Estructura
cesta-app/
├── apps/api/ # Nest (paquete independiente)
├── apps/web/ # Angular + Tailwind (paquete independiente)
└── package.json # scripts api / start · apps independientes
D-P1-03: npm --prefix o pnpm --filter. Prohibido npm workspaces que rompen Angular (AP-06).
Scripts raíz esperados:
| Script | Acción |
|---|---|
| api | Arranca Nest en :3016 |
| start / web | Arranca Angular en :4200 |
3. API pública vs JWT
| Método | Ruta | Auth |
|---|---|---|
| GET | /api/needs | Público |
| GET | /api/needs/:slug | Público |
| POST | /api/requests | Público |
| POST | /api/auth/login | Público |
| GET | /api/requests | JWT STAFF |
| PATCH | /api/requests/:id | JWT STAFF |
CORS: origen http://localhost:4200 en local.
[COMPROBADO] Superficie API del encargo. No se añaden Must extra (/stats, POST /needs).
4. Justificación
| Elección | Razón |
|---|---|
| Angular + Nest | Alineación con la serie daily y el handoff del estudio |
| Neon serverless | Volumen bajo (S1: 8–20 líneas; demo 5); no cola Redis |
| JWT STAFF | D-P1-05: panel interno cerrado desde el día 1 |
| Agregados de hueco en servidor | Source of truth = SQL SUM; la tabla no inventa stock |
| Puerto 3016 | Evitar colisión con RONDA :3015 / DERIVA :3014 / PIZARRA :3013 |
5. Variables
.env local (nunca git):
| Variable | Servicio | Descripción |
|---|---|---|
DATABASE_URL | API | Neon jolly-night-99671179 |
JWT_SECRET | API | Firma tokens |
PORT | API | opcional, 3016 |
JWT_EXPIRES | API | opcional (p. ej. 12h) |
.env.example solo placeholders.
Web: URL de API en ApiService (default http://localhost:3016/api).
6. Dependencias de producto (mínimas)
API
@nestjs/common/core/platform-express@nestjs/jwt(o verificación manual)bcrypt(obcryptjs)- cliente Postgres (
pg) o Prisma class-validator+class-transformer
Web
- Angular standalone
- Tailwind 3
- fuentes: Spectral + Manrope (Google Fonts o self-host)
7. Arranque (contrato)
cd /Users/cristian/orca/cesta-app
# instalar apps independientes (npm --prefix o pnpm --filter)
# apps/api/.env → DATABASE_URL + JWT_SECRET + PORT=3016
# migrate / ensureSchema + seed
# api → http://localhost:3016
# web → http://localhost:4200
8. Lo que este stack no es
| No | Por qué |
|---|---|
| npm workspaces | AP-06 |
| Firebase Auth | Stack fijo JWT + Neon |
| Next.js / React | D-P0-03 |
| Pasarela de pago | S4 |
| Elasticsearch | 5 filas no lo necesitan |
9. Nota sobre el repo existente
[COMPROBADO] Existe /Users/cristian/orca/cesta-app y el remoto Criscode2022/cesta-app.
El scaffold puede contener restos de un producto anterior (rutas /mesas, GET /tables). Esta suite define el contrato CESTA; el código debe alinearse a las rutas y API de los docs 03–05, no al leftover. [DECISIÓN]
07-creative-direction.md
07 — Dirección creativa · CESTA
Mood: botanical (musgo wash #E6EFDD × soil). No charity naranja. Superficie = tabla, no hero 2-col.
Spectral display + Manrope UI. Rail 72px tierra. Hueco en #B23A2F. Fotos de despensa reales por línea.
08-design-system.md
08 — Design system · CESTA
Tokens: bone #E6EFDD soil #1C2418 moss #3D6B4A leaf #7A9A6A sack #C4B49A alert #B23A2F.
Componentes: rail, tabla (5 columnas fijas), fila con foto 56×40, CTA suelo, estados empty/loading/error.
09-content-guide.md
09 — Guía de contenido — CESTA
1. Voz
Cercana de barrio, concreta, de mostrador. Tuteo. Segunda persona.
Sin jerga de fundraising (“impacto premium”), sin sede electrónica, sin “haz tu donación ahora”.
El producto informa y recoge un compromiso, no vende una causa.
2. Palabras permitidas / prohibidas
| Usar | No usar |
|---|---|
| Compromiso / comprometer | Reserva, donación online, “ya está entregado” |
| Coordinación te escribe | “Caja garantizada”, “ticket emitido” |
| Hueco | Stock, inventario, SKU |
| Necesidad / línea | Producto, item shop, campaña |
| En especie / traer | Bizum, socio, certificado fiscal |
| Lo que falta, en una lista | “Descubre nuestro ecosistema de impacto” |
| Código CE- | Referencia UUID, localizador |
| Recibido / RECEIVED | Inscrito, sentado, enrolled |
| Coordinadora / STAFF | Community manager, fundraiser |
3. Microcopy clave (literales de producto)
| Superficie | Copy |
|---|---|
| Eslogan | Lo que falta, en una lista. |
| Home | Pedido, comprometido y hueco de esta semana. |
| Fila CTA implícito | Toda la fila es el enlace; no “Ver más” |
| Disclaimer ficha | Comprometer no confirma la entrega. La caja se cuenta cuando llega al local. |
| Form título | Comprometer {título} |
| Form submit | Enviar compromiso |
| Form disclaimer | Esto es un compromiso, no una entrega confirmada. |
| Success titular | Compromiso anotado. |
| Success cuerpo | Guarda este código. Todavía no has entregado. Inés te contacta antes de abrir. |
| Success código | CE-0816-001 (seleccionable) |
| Empty tabla | Ahora mismo no hay líneas abiertas. |
| Empty inbox | Nadie se ha comprometido todavía. |
| Error tabla | No hemos podido cargar la lista. |
| Error retry | Reintentar |
| Login titular | Coordinación |
| Login submit | Entrar |
| Login error | Email o contraseña no valen. |
| Inbox titular | Compromisos |
| PATCH CONTACTED | Marcar contactado |
| PATCH RECEIVED | Recibido |
| PATCH PARTIAL | Parcial |
| PATCH CANCELLED | Cancelar |
| Rail Lista | Lista |
| Rail Inbox | Inbox |
| Salir | Salir |
[DECISIÓN] El disclaimer aparece tres veces: ficha, form, success. No es opcional.
4. Códigos
Formato CE-MMDD-NNN. Se lee en voz alta en el mostrador: “ce, ocho dieciséis, cero cero uno”.
No UUID. No QR obligatorio en v1.
5. Nombres propios del seed
| Nombre | Uso |
|---|---|
| Rosa Belmonte | VECINO; compromiso aceite |
| Inés Marzal | STAFF; copy de “te escribe Inés” en success de seed |
No inventar reseñas, CIF ni horarios oficiales de una despensa real. [SUPUESTO de catálogo demo]
Franja demo: sábado 10–13. No se afirma como horario de un local existente.
6. Tono por estado
| Status | Frase inbox (STAFF) | Frase que Inés podría mandar (fuera de app) |
|---|---|---|
| NEW | Sin contacto | — |
| CONTACTED | Escrita / llamada | “He visto tu CE-0816-001. ¿Traes los 3 L el sábado?” |
| RECEIVED | Caja aquí | “Recibido. Gracias. Trae el código por si acaso.” |
| PARTIAL | Llegó menos | “Anotamos parcial. Si puedes completar, avísame.” |
| CANCELLED | Fuera | “Cancelamos este compromiso.” |
La app no envía esos mensajes en v1. El copy solo prepara el habla.
7. Capitalización y números
- Wordmark: CESTA en versales.
- Cantidades:
40 kg,24 L,36 ud,12 packs(espacio fino entre número y unidad). - Hueco:
21 L(misma unidad que el pedido). - No mezclar “litros” en UI si la unidad es
L; en ficha se puede expandir “litros”.
8. Idioma
es-ES. Fácil lectura. EN no en v1.
No mezclar “need” en UI. En API sí: needSlug, needs.
9. Accesibilidad de copy
- No transmitir estado solo con color (“el rojo significa parcial”).
- Badge lleva texto: Nuevo, Contactado, Recibido, Parcial, Cancelado.
- Alt de fotos: “Botellas de aceite de oliva”, no “imagen1”.
10. Piezas que no se escriben
| No | Motivo |
|---|---|
| Importes, IBAN, “dona 10 €” | CESTA no cobra |
| “Entrega confirmada al instante” | Rompe H2 |
| Ranking de donantes | Reputación, otro producto |
| Lorem / “lorem ipsum aceite” | Prohibido en el case |
10-accessibility.md
10 — Accesibilidad — CESTA
Objetivo: WCAG 2.2 AA. No se declara conformidad legal certificada.
Este documento cubre la UI (tabla, form, rail, inbox). No audita la accesibilidad física del local demo. [SUPUESTO]
1. Decisiones
| Tema | Decisión |
|---|---|
| Tipo UI | Manrope ≥16px body |
| Display | Spectral no se usa por debajo de 24px |
| Contraste | Tierra #1C2418 sobre #E6EFDD / #C4B49A |
| CTA musgo | Texto hueso sobre #3D6B4A |
| Ficha / PARTIAL | Texto hueso ≥14px bold |
| Foco | Outline 2px musgo, offset 2px, visible (no outline-none global) |
| Rail | 3 botones reales (<a> / <button>), no divs clicables |
| Tabla desktop | <table> + <caption> o aria-label; cabeceras <th scope="col"> |
| Tabla móvil | Lista de <article> apilados, no tabla de 5 columnas ilegible |
| Imágenes | alt descriptivo del ítem |
| Form | Labels visibles, no placeholder-only |
| Errores | Texto, no solo color |
| Mobile | Targets ≥44px (rail 48×48, CTA h-44) |
| Movimiento | Fade 160ms; prefers-reduced-motion: reduce → 0 |
| Live regions | aria-live="polite" en empty/error de la tabla |
2. Contraste (comprobación de diseño)
| Par | Uso | Nota |
|---|---|---|
#1C2418 / #E6EFDD | Body | Pasa AA y AAA cuerpo |
#1C2418 / #C4B49A | Thead / rail idle | Pasa AA |
#E6EFDD / #3D6B4A | CTA / RECEIVED | Pasa AA para texto ≥14px bold |
#E6EFDD / #1C2418 | Rail activo | Pasa AA |
#E6EFDD / #B23A2F | Badge PARTIAL | Verificar ≥14px bold; no usar en 12px |
#7A9A6A como texto 13px sobre hueso | — | Prohibido para body |
3. Teclado
| Flujo | Orden |
|---|---|
| Tabla | Wordmark → primera fila → … → rail (Lista, Inbox, Entrar) |
| Ficha | Back → CTA → rail |
| Form | Nombre → email → cantidad → nota → submit → rail |
| Inbox | Lista (cada fila / acciones) → rail |
| Login | Email → password → submit |
- Enter en fila = navegar a ficha.
- Escape no cierra el rail (no es modal).
- Focus trap solo si hubiera modal; v1 no tiene modal salvo confirmación CANCELLED (Should).
4. Semántica
| Pieza | Markup |
|---|---|
| Tabla desktop | <table> con caption “Necesidades de la despensa”; fila = enlace único o tr + un a que cubre |
| Tabla móvil | <ul> de cards; un <a> por ítem |
| Cifras | pedido, comprometido, hueco anunciados con unidad |
| Rail | <nav aria-label="Principal"> |
| Slot activo | aria-current="page" |
| Form | <form> + <label for> |
| Disclaimer | No es alert; es texto normal + borde |
| Success código | <p><code> seleccionable |
| Badges | texto visible; no solo icono |
| Skeletons | aria-busy="true" en el contenedor; aria-hidden en placeholders |
5. Lector de pantalla — copy
| Situación | Anuncio |
|---|---|
| Fila | “Aceite de oliva, pedido 24 litros, comprometido 3, hueco 21” |
| CTA | “Comprometer aceite de oliva” |
| Success | “Compromiso anotado. Código ce ocho dieciséis cero cero uno. Todavía no has entregado.” |
| Error | Anuncio en live region; foco al banner |
6. Producto vs local físico
| Lo que CESTA puede hacer | Lo que no afirma |
|---|---|
| Unidad, pedido, hueco, código oral | Rampa, mostrador a altura, bucle magnético del local |
| Form usable con teclado y zoom 200% | Que el local demo sea accesible |
[DECISIÓN] v1 no incluye faceta a11y de sede (eso era PIZARRA). El campo note permite “voy con carrito” sin fingir auditoría.
7. Riesgos
| Riesgo | Mitigación |
|---|---|
| Tabla de 5 columnas en 390 | UI-10: apilar; no scroll horizontal ciego |
| Rail tapa el CTA en móvil | Padding-bottom del main ≥ 72px |
| Spectral en meta 12px | Prohibido; meta = Manrope 14 |
| Musgo como único estado activo | + aria-current + label “Lista” |
| Foto sin alt | Checklist seed: 5 alts |
| Status bar iOS en Paper | No es producto |
8. QA a11y (mínimo)
| # | Prueba | Pasa si |
|---|---|---|
| 1 | Teclado tabla → ficha → form → success | Sin trampa; foco visible |
| 2 | Teclado login → inbox → PATCH | Acciones alcanzables |
| 3 | Zoom 200% móvil 390 | Rail usable; no solapa inputs |
| 4 | Lighthouse a11y ≥ 90 en / | Sin contrast fails de tokens |
| 5 | VoiceOver/NVDA: hueco anunciado | Fila comprensible |
| 6 | prefers-reduced-motion | Sin stagger |
| 7 | Contraste badges PARTIAL / RECEIVED | Texto hueso bold ≥14 |
9. Criterios de aceptación
- Ningún control del rail o CTA mide menos de 44×44 CSS px.
- El disclaimer no depende del color musgo para ser entendido.
- Las 5 fotos seed tienen
altno vacío y no genérico. - El foco no se pierde al reintentar un error de red.
- En 390 la tabla no obliga a scroll horizontal para leer el hueco.
11-privacy-security.md
11 — Privacidad y seguridad — CESTA
No es un dictamen legal ni un DPIA. Es el contrato de producto para el vertical slice.
1. Datos
| Dato | Tabla | Clasificación | Uso |
|---|---|---|---|
| Nombre, email | requests | PII | Contactar compromiso |
| Nota | requests | PII opcional | Contexto STAFF |
| quantity | requests | Operativo | Hueco |
| Need, estado, código | requests + needs | Operativo | Cola y acuse |
| Email STAFF, password hash | users | Credencial | Solo STAFF |
| token_hash, expires_at | sessions | Secreto de sesión | Logout / caducidad |
Sin datos de salud, menores con tutor, geolocalización, ni pagos.
[DECISIÓN] El vecino no crea cuenta. Menos superficie de credenciales.
2. Base (supuesto de diseño)
[SUPUESTO] Interés de la despensa en coordinar entregas en especie + consentimiento de contacto al enviar el form.
El texto del form debe mencionarlo:
Usaremos tu nombre y email solo para escribirte por este compromiso.
No se cede a redes de fundraising, no se usa para marketing de terceros, no se pide IBAN.
3. Minimización
| Recogemos | No recogemos en v1 |
|---|---|
| name, email | DNI, fecha de nacimiento |
| note libre | Dirección de casa |
| quantity | Foto de perfil, DNI fiscal |
| — | Teléfono (no está en el contrato del POST) |
[COMPROBADO] Body público = { needSlug, name, email, quantity, note }. Sin phone.
4. Retención
| Dato | Hipótesis operativa |
|---|---|
| Compromisos | 12 meses desde created_at; luego borrado o anonimización (name → “—”, email hash) |
| Sessions | Hasta expires_at o logout |
| Needs | Mientras la línea esté abierta (archivo = L1+) |
| Demo Neon | No es producción; se puede resetear |
[HIPÓTESIS] 12 meses cubre una temporada de despensa y reclamaciones de “yo escribí”. No es plazo legal afirmado.
5. Autorización
| Recurso | Público | STAFF JWT |
|---|---|---|
| GET needs / :slug | Sí | Sí |
| POST requests | Sí | Sí |
| GET / PATCH requests | 401 | Sí |
| Password hashes | Nunca en response | Nunca |
Guards en API son la autoridad. La UI solo esconde.
[COMPROBADO] D-P1-05: panel interno cerrado desde el día 1.
JWT
- Login válido → bcrypt compare → emite JWT (
sub, email, role). - Request protegida → verifica firma.
- Logout → limpia cliente; si hay
sessions, se invalida elsid.
[DECISIÓN] Bearer header, no cookie de primer partido en v1 (demo localhost). XSS sigue siendo el riesgo (no innerHTML de notas sin escape).
6. Seguridad v1
| Control | Detalle |
|---|---|
| TLS hacia Neon | Connection string sslmode |
| Validación DTOs | class-validator |
| Password | bcrypt cost ≥ 10 |
| Secretos | Fuera de git; .env local |
| Público no lista PII | GET needs sin emails de requests |
| Rate limit POST | L1+; documentado como hueco |
| Honeypot | L1+ |
| CORS | Origen web conocido |
7. Amenazas y respuesta
| Amenaza | Impacto | Respuesta v1 |
|---|---|---|
| Enumeración de inbox | PII de vecinos | 401 sin token |
| Spam de compromisos | Cola inútil | Validación; rate-limit L1+ |
| Token robado en localStorage | Suplantación STAFF | Expiración corta |
| Overposting status | Caos de hueco | Enum cerrado; H3 operativa |
IDOR :id | Ver PII ajena | Un solo STAFF en v1; igual exigir JWT |
| Seed en producción | Credencial conocida | Rotar JWT_SECRET y password si se publica |
Lookup /ok/:code como ficha PII | Filtración | Success no lista email; no hay GET público por código |
8. Credencial demo
| Campo | Valor |
|---|---|
ines@cesta.barrio | |
| Password | password123 |
[COMPROBADO] Encargo. No reutilizar en un deploy público sin rotar.
9. Riesgos residuales (aceptados L1)
- No hay checkbox RGPD formal (Q3).
- No hay cifrado de columna PII (TLS en tránsito + Neon en reposo).
- localStorage XSS.
- Un STAFF ve todas las solicitudes (cola única de demo).
10. Criterios de aceptación
- GET
/api/requestssinAuthorization→ 401. - PATCH
/api/requests/:idsin token → 401. - GET
/api/needsno incluye emails de vecinos. - Response de login no incluye
password_hash. .envno se commitea./ok/:codeno revela email ni nota.
12-analytics.md
12 — Analítica — CESTA
Instrumentación modelo. v1 puede no emitir eventos reales; este doc es el contrato para cuando se encienda.
No se presentan tasas de conversión inventadas.
1. North star
% de compromisos válidos (nombre + email + need existente) que pasan a CONTACTED antes de la franja de entrega (sábado demo).
Por qué no “kilos recaudados” ni “pageviews”:
| Métrica vanidosa | Por qué no es éxito |
|---|---|
| Visitas a la tabla | Un cartel de WhatsApp también se mira |
| Hueco 0 | Puede ser compromiso fantasma (falla H2) |
| Cuentas creadas | El vecino no tiene cuenta |
| € donados | CESTA no cobra |
La north star mide contacto honesto a tiempo, alineada a S3/H2.
2. Hipótesis ↔ señales
| ID | Hipótesis | Evento / invariante |
|---|---|---|
| H1 | La tabla reduce “¿qué llevo?” vs WhatsApp | % sesiones con need_opened |
| H2 | Copy “compromiso ≠ entrega” baja no-shows | ↓ tickets “pensé que ya contaba”; ratio RECEIVED / NEW |
| H3 | RECEIVED nunca inventa stock | requested_qty inmutable; hueco = pedido − comprometido |
3. Eventos
| Evento | Dónde | Props (sin PII en claro) |
|---|---|---|
table_viewed | / | result_count |
need_opened | Fila / ficha | slug |
commit_started | Form | slug |
commit_submitted | POST 201 | code, slug, quantity |
commit_failed | POST error | reason (validation/network/4xx) |
staff_login | Login 200 | — |
staff_login_failed | 401 | — |
inbox_viewed | /inbox | count |
status_changed | PATCH 200 | from, to, slug |
[DECISIÓN] No enviar email a analytics. El code es el identificador.
4. Funnel
Land tabla → need_opened → commit_started → commit_submitted
→ staff_login → status_changed(CONTACTED) ≤ franja
→ status_changed(RECEIVED) | PARTIAL
| Paso | Definición |
|---|---|
| Land | table_viewed |
| Interés | need_opened / table_viewed → H1 |
| Intención | commit_started / need_opened |
| Captura | commit_submitted / commit_started |
| Ops | CONTACTED antes de la franja → North star |
| Cierre | RECEIVED o PARTIAL; pedido inmutable → H3 |
5. Invariantes (salud, no vanity)
| Check | Query conceptual | Alerta |
|---|---|---|
| H3 | requested_qty cambió tras un RECEIVED | Cualquier need |
| Códigos únicos | duplicate code | > 0 |
| 401 inesperados | GET needs 401 | No debe ocurrir |
| 5xx | cualquier ruta | > 1% |
6. Dimensiones
| Dimensión | Valores |
|---|---|
| slug | arroz, aceite, tomate, panales, leche |
| unit | kg, L, ud, packs |
| viewport | mobile ≤480, desktop |
Sin user-id de vecino.
7. Privacidad de analítica
- Sin fingerprinting.
- Sin email en claro.
- IP no se guarda en el plano de producto.
- Eventos de demo pueden quedarse en consola.
8. Qué no medimos en v1
| No | Motivo |
|---|---|
| Heatmaps | Craft, no research de campo fingido |
| NPS | Muestra nula |
| “Kilos en almacén” | No hay inventario ERP |
| Followers de la ONG | No es red social |
| Importe donado | No hay dinero |
9. Criterios de aceptación (si se implementa tracker)
- Cada evento de la tabla §3 tiene nombre estable snake_case.
commit_submittedincluyecodecon prefijoCE-.- Ningún payload de analytics incluye
email. - El dashboard interno (si existe) no es Must del L1.
13-qa-test-plan.md
13 — Plan de pruebas — CESTA
1. Smoke obligatorio (D-P1-06)
| # | Acción | Esperado |
|---|---|---|
| 1 | GET /api/needs | 200 · array length ≥ 5 · slugs seed presentes |
| 2 | POST /api/requests { needSlug, name, email, quantity, note } | 201 · code con prefijo CE- · status=NEW |
| 3 | POST /api/auth/login ines@cesta.barrio / password123 | 200 · accessToken · user.role=STAFF |
| 4 | GET /api/requests con Bearer | 200 · incluye el POST reciente |
| 5 | ng build o serve web | Web arranca o build OK |
[COMPROBADO] Smoke = GET needs + POST request + login JWT.
2. Casos funcionales
| ID | Caso | Esperado |
|---|---|---|
| S1 | Slugs seed | GET needs incluye arroz, aceite, tomate, panales, leche |
| S2 | Aceite | GET /api/needs/aceite 200 · unit L · requestedQty 24 |
| S3 | Slug inexistente | 404 |
| S4 | Compromiso email inválido | 400; form no navega a /ok/… |
| S5 | Compromiso quantity ≤ 0 | 400 |
| S6 | Success | Código visible en /ok/:code; copy “todavía no has entregado” |
| S7 | Inbox sin JWT | 401 / redirect login |
| S8 | PATCH CONTACTED | 200 · badge Contactado |
| S9 | PATCH RECEIVED | 200 · requestedQty del need no cambia |
| S10 | PATCH PARTIAL | 200 |
| S11 | PATCH CANCELLED | 200 · comprometido deja de sumar esa quantity |
| S12 | Login malo | 401 · mensaje en form |
| S13 | Empty tabla (DB sin needs) | UI-07 |
| S14 | API caída | UI-08 + Reintentar conserva la vista |
| S15 | Loading | UI-09 skeletons antes del primer paint |
| S16 | Mobile 390 | Tabla apilada; CTA ≥44px; no hero 2-col |
| S17 | Fotos seed | 5 image_key resuelven a assets |
| S18 | Código formato | regex ^CE-\d{4}-\d{3}$ |
| S19 | GET requests no filtra PII al público | sin token 401 (no 200 []) |
| S20 | Password no viaja de vuelta | login response sin hash |
| S21 | Body POST | acepta needSlug, name, email, quantity, note |
| S22 | Hueco | gap === max(0, requestedQty - committedQty) |
3. Datos de prueba
| Uso | Valor |
|---|---|
| STAFF | ines@cesta.barrio / password123 |
| VECINO | Rosa Belmonte · rosa.belmonte@example.com |
| Need feliz | aceite |
| Need parcial demo | tomate (seed PARTIAL) |
| Código seed | CE-0816-001 |
4. Regresión de diversidad / craft
| Check | Falla si |
|---|---|
| Home es tabla | Hay hero 2-col + 3 cards de marca (AP-12) |
| Shell es rail | Hay dock flotante, sidebar CRM o command search como nav primaria |
| Copy honesto | Success dice “entrega confirmada” o “donación recibida” |
| Paleta | Se cuelan naranja ONG / cromo RONDA / chalky PIZARRA |
| Tipo | Zilla / Mulish / Newsreader / Atkinson / Outfit |
| Rutas | Aparecen /mesas, /buscar, /donar, /inbox/:id |
5. Hipótesis (no se “prueban” en la demo)
H1, H2, H3 requieren uso real. El QA verifica que existen las superficies que permitirían medirlas (tap en fila, disclaimer, badge RECEIVED + pedido inmutable).
6. Criterios de salida QA v1
- Smoke §1 en verde.
- S2, S6, S7, S9, S16, S18, S21 en verde.
- Cero blockers de copy deshonesto.
- Sin
.enven el repo.
7. Fuera de este plan
Playwright e2e completo, carga, fuzzing, auditoría del local físico.
14-dev-handoff.md
14 — Handoff desarrollo — CESTA
1. Arranque
cd /Users/cristian/orca/cesta-app
# apps independientes (npm --prefix o pnpm --filter)
# apps/api/.env
# DATABASE_URL= # Neon jolly-night-99671179
# JWT_SECRET=
# PORT=3016
# migrate / ensureSchema + seed
# api → http://localhost:3016
# web → http://localhost:4200
Demo: ines@cesta.barrio / password123.
2. Paridad Paper
| Debe verse en Angular | Artboard |
|---|---|
| Tokens botanical + Spectral/Manrope | UI-00 |
| Tabla densa + rail | UI-01, UI-10 |
| Ficha con foto real | UI-02 |
| Compromiso + disclaimer | UI-03 |
Success CE- en /ok/:code | UI-04 |
| Login STAFF | UI-05 |
| Inbox estados | UI-06 |
| Empty / error / loading | UI-07…09 |
No clonar RONDA (feed + dock), DERIVA (mapa split) ni PIZARRA (command search).
3. Contratos
| Tema | Contrato |
|---|---|
| Login | { accessToken, user } camelCase (no access_token) |
| Tabla | GET /api/needs → array con requestedQty, committedQty, gap |
| Ficha | GET /api/needs/:slug |
| Compromiso | POST /api/requests { needSlug, name, email, quantity, note } |
| Inbox | GET /api/requests Bearer |
| Status | PATCH /api/requests/:id { status } |
| Código | CE-MMDD-NNN Europe/Madrid |
| Status enum | NEW | CONTACTED | RECEIVED | PARTIAL | CANCELLED |
Campos JSON en camelCase hacia el web aunque SQL sea snake_case. [DECISIÓN]
4. Mapa de rutas web
| Path | Guard cliente | Página |
|---|---|---|
/ | no | Tabla |
/necesidades/:slug | no | Ficha |
/necesidades/:slug/comprometer | no | Form |
/ok/:code | no | Success |
/login | no | Login |
/inbox | soft (redirect) | Inbox |
Soft guard: si no hay token, /login?next=. La API es la autoridad.
[COMPROBADO] Solo estas seis rutas. No implementar /inbox/:id, /mesas, /donar.
5. Assets
Copiar desde el case:
2026-08-16-cesta/assets/hero.jpg
2026-08-16-cesta/assets/needs/*.jpg
→ apps/web/public/assets/needs/ (o src/assets/needs/)
image_key del seed = filename (arroz.jpg, …).
6. DoD implementación L1
| Check | OK |
|---|---|
| API sirve :3016 | |
| Seed 1 STAFF + 5 needs | |
| Smoke GET + POST + login | |
| JWT protege GET/PATCH requests | |
| Web tabla + rail + ficha + form + success + inbox | |
| Disclaimer ×3 | |
| Tokens Tailwind = paleta brief | |
ng build o equivalente OK | |
| README del repo app con credencial | |
| Home es tabla (no leftover feed) |
7. No hacer
| Prohibido | Motivo |
|---|---|
| npm workspaces | AP-06 |
Commit de .env | Secreto |
| Prometer entrega en copy | H2 / AP-09 |
Estado SEATED / ENROLLED / WAITLIST | Vocabulario de otros días |
| Search bar o mapa como home | Rompe H-TABLE |
| Hero 2-col + 3 cards | AP-12 |
image_gen como captura de producto | D-P0-09 |
Body POST con tableSlug / partySize | Leftover RONDA |
8. Contactos de diseño (artefactos)
| Artefacto | Path |
|---|---|
| Brief | ux-projects/2026-08-16-cesta/docs/00-day-brief.md |
| Paper | https://app.paper.design/file/01M04P0VFWGX1DQVSCR5TPWM9Z |
| Datos | docs/05-data-model.md |
| Flujos | docs/04-user-flows.md |
| IX | docs/16-interaction-specs.md |
| Build | docs/20-implementation.md |
15-roadmap.md
15 — Roadmap — CESTA
1. Hecho en L1 (esta ejecución documental)
| Entrega | Evidencia |
|---|---|
| Definición producto + terna | docs 00-brief, 01 |
| Personas Rosa / Inés, JTBD, journey | docs 02 |
| IA table + rail | docs 03 |
| Flujos F-LEAD + STAFF | docs 04 |
| Modelo needs / requests / users + seed 5 | docs 05 |
| Stack Angular/Nest/Neon/JWT :3016 | docs 06, 20 |
| Dirección botanical + DS | docs 07, 08 |
| Copy honesto compromiso ≠ entrega | docs 09 |
| Paper ref 12 UX + 11 UI | docs 00-paper-reference |
| Suite 00–20 + README + executive | case folder |
2. Implementación L1 (misma complejidad, código)
Contrato en docs 14 y 20. No es deuda silenciosa del case documental: es el build del repo cesta-app.
| Ítem | Prioridad |
|---|---|
| Schema + seed 5 needs | P0 |
| Endpoints Must + smoke | P0 |
| Web tabla/ficha/form/success/login/inbox | P0 |
| Rail + tokens botanical | P0 |
Sustituir leftover /mesas / /tables | P0 |
Guards Angular canActivate | P1 |
3. L1+ (misma complejidad, polish)
| Ítem | Notas |
|---|---|
| Rate limit + honeypot POST requests | Anti-spam |
| Checkbox privacidad | Q3 |
| Ajuste numérico en PARTIAL | Q5 |
| Filtros de status en inbox | Query API |
| Playwright S1–S22 | CI |
| Copy code clipboard | Could |
| mailto en inbox | Ops |
| API URL por environment | Deploy |
prefers-reduced-motion verificado | a11y |
Estas son subidas de pulido, no parches del L1 documental.
[DECISIÓN] No dejar backlog del vertical slice como “mañana”. Lo de arriba es polish o subida de nivel.
4. L2 / L3 (requiere brief nuevo de diversidad)
- Multi-sede / varias despensas
- Cuenta VECINO y “mis compromisos”
- Email / WhatsApp transaccional
- CMS de necesidades
- Inventario ERP (lotes, caducidad)
- Check DB transaccional de hueco
- Audit log
- Search o mapa como home (rompería terna)
5. Explícitamente fuera (no backlog disfrazado)
| Ítem | Por qué |
|---|---|
| Pagos / Bizum / socio / certificado fiscal | S2 / S4 |
| Feed de impacto | Terna ≠ RONDA |
| Mapa de despensas | Terna ≠ DERIVA |
| Search-first home | Terna ≠ PIZARRA |
| Kanban | ≠ PRIMA |
| CRM de donantes | S3 |
| App nativa | Fuera de stack daily |
6. Orden de ataque si hay continuidad de código
- Contratos 05–06 + seed 05 + smoke 13
- Web paridad UI-00…10 (sustituir leftover)
- Guards + mailto + clipboard
- Rate limit si hay tráfico
- Evaluar brief L2 (no parche silencioso)
16-interaction-specs.md
16 — Especificación de interacción — CESTA
1. Convenciones
| Token | Valor |
|---|---|
| Duración corta | 120–160ms |
| Easing | ease-out |
| Reduced motion | 0ms |
| Target | ≥44×44 |
| Pending | control disabled + aria-busy |
2. Rail (S-RAIL)
| Gesto | Resultado |
|---|---|
| Tap slot | Navega; aria-current se mueve |
| Tap Inbox sin sesión | /login?next=/inbox |
| Scroll de la tabla | Rail no se esconde en v1 |
| Teclado | Tab a los slots al final (desktop: rail es el primer landmark o el último; [DECISIÓN] rail primero en desktop, último en 390) |
| Rotación landscape 390 | Rail compacto; main padding-bottom 72 |
Estado activo: disco tierra + icono hueso.
Estado default: tierra sobre saco/hueso.
Slots: Lista / · Inbox /inbox · Entrar /login (o Salir si auth).
3. Tabla home
| Gesto | Resultado |
|---|---|
| Tap fila | /necesidades/:slug (fila completa clicable, un solo enlace) |
| Pull-to-refresh | No en v1; Reintentar solo en error |
| Primera carga | 5 skeletons de fila; no spinner centrado de marca |
| 0 resultados | Empty UI-07 |
| Error | Banner fijo bajo wordmark + Reintentar (refetch) |
| Hover desktop | Fondo saco/20; no elevación de card |
AC: la primera fila (foto + hueco) está en el viewport inicial a 390 y a 1280 (sin hero de 60vh).
Orden visual = seed / title ASC. No “destacados”.
Columnas desktop: foto · ítem · pedido · comprometido · hueco.
Numéricos: tabular-nums, alineados a la derecha, unidad visible.
390 (UI-10): pila — [foto 56] [título] + fila de tres metas pedido / comprometido / hueco.
4. Ficha
| Gesto | Resultado |
|---|---|
| CTA | “Comprometer” → /necesidades/:slug/comprometer |
| Hueco 0 | Mismo CTA; copy avisa |
| Back / wordmark | Tabla / |
| Foto | No lightbox v1 |
| Disclaimer | Visible antes del CTA (no debajo del fold en desktop) |
5. Form compromiso
| Campo | Interacción |
|---|---|
| name | Autocomplete name |
type=email, autocomplete email | |
| quantity | type=number · min > 0 · step 1 si ud/packs; 0.5 o 1 si kg/L |
| note | textarea 3 filas |
| Gesto | Resultado |
|---|---|
| Submit inválido | Foco al primer campo error; no POST |
| Submit válido | Botón disabled; POST; 201 → /ok/:code |
| Error red | Botón “Reintentar”; no limpia campos |
| Doble tap | Ignorado mientras pending |
Submit deshabilitado si nombre < 2, email inválido o quantity ≤ 0.
6. Success
| Gesto | Resultado |
|---|---|
| Long-press / select | Código seleccionable |
| Copiar (Could) | navigator.clipboard; toast 1.5s “Código copiado” |
| CTA primario | Volver a la tabla / |
| CTA secundario | Volver a la ficha |
Sin animación de confetti. Sin “¡entrega confirmada!”.
El :code de la ruta se pinta tal cual. No lookup PII.
7. Login
| Gesto | Resultado |
|---|---|
| Submit | POST login; pending en botón |
| 200 | Guarda token + user; navega next sanitizado o /inbox |
| 401 | Texto bajo el form; password no se limpia |
| Enter | Submit |
next permitido: paths que empiezan por / y no // ni http.
8. Inbox
| Gesto | Resultado |
|---|---|
| Sin token | Redirect login |
| Acciones | En la fila: Contactado / Recibido / Parcial / Cancelar |
| Empty | Titular + 1 línea |
| Error 401 mid-session | Redirect login |
| Error red | Banner + Reintentar |
No swipe-to-archive. No navegación a /inbox/:id.
9. Estados (acciones en lista)
| Acción | Confirmación | Resultado |
|---|---|---|
| Contactado | No | PATCH CONTACTED |
| Recibido | No | PATCH RECEIVED |
| Parcial | No | PATCH PARTIAL |
| Cancelar | Sí (“¿Cancelar CE-…?”) | PATCH CANCELLED |
Optimistic UI: badge cambia al tap; si PATCH falla, rollback + toast error.
RECEIVED no anima un “stock +N”. H3.
10. Motion
| Superficie | Spec |
|---|---|
| Filas tabla | Fade 160ms, stagger ≤30ms (off si reduced-motion) |
| Cambio de badge | Crossfade 120ms |
| Rail | Sin hide; sin bounce |
| Skeletons | Estáticos o pulso 1.2s opacity 0.6–1; off si reduced-motion |
Prohibido: scroll-jacking, page transitions de 400ms, parallax en foto.
11. Criterios de aceptación de interacción
- Un VECINO en 390px llega de tabla a success en ≤ 4 taps (fila, CTA, submit → success).
- Un STAFF en 390px marca CONTACTED en ≤ 3 taps desde inbox (acción en fila).
- Ningún submit permite doble POST.
- El rail no cubre el CTA de ficha (padding).
- Reduced-motion elimina stagger.
- La home no es hero 2-col (AP-12).
- En 390 el hueco se lee sin scroll horizontal.
17-prototype-map.md
17 — Mapa de prototipo — CESTA
Paper no es clicable vía MCP. El prototipo vivo es la app Angular.
| Ruta | Paper | Rol |
|---|---|---|
/ | UI-01, UI-07, UI-08, UI-09, UI-10 | VECINO |
/necesidades/:slug | UI-02 | VECINO |
/necesidades/:slug/comprometer | UI-03 | VECINO |
/ok/:code | UI-04 | VECINO |
/login | UI-05 | STAFF |
/inbox | UI-06 | STAFF |
Proceso: UX-00…UX-11. Tokens: UI-00.
Recorrido demo (guión)
- Abrir
/— tabla de 5 líneas, rail Lista activo. - Tocar Aceite de oliva — ficha + disclaimer.
- Comprometer — form 3 L, Rosa Belmonte.
- Success
/ok/CE-…— código + “todavía no has entregado”. /login—ines@cesta.barrio/password123./inbox— marcar Contactado / Recibido / Parcial.
Estados en el mismo prototipo
| Estado | Cómo provocarlo |
|---|---|
| Empty tabla | API 200 [] |
| Empty inbox | lista vacía autenticada |
| Error | API caída / 5xx |
| Loading | throttle de red |
| Mobile tabla | 390 × 844 |
Fuera del prototipo clicable
- Paper artboards son estáticos.
- WhatsApp / llamada de Inés (backstage).
- Entrega física del sábado.
- Lookup público de PII por código.
18-completeness-audit.md
18 — Auditoría de completitud — CESTA (2026-08-16)
1. Alcance auditado
Vertical slice L1: tabla densa de necesidades de despensa vecinal + compromiso público + panel STAFF JWT, con docs, referencia Paper, contratos Angular + Nest + Neon.
Este encargo cubre la suite documental. Paper hi-fi y el repo cesta-app pueden existir en paralelo; la auditoría marca lo que esta suite cierra. El day brief no se ha sobrescrito.
2. Checklist CRON / ALS-2
| Requisito | Estado | Evidencia |
|---|---|---|
| Diversidad sector/tipo/nivel | OK | ONG despensa L1; no feed; no mapa; no search; no pagos |
| Terna 0 códigos = N−1 · ≥2 vs N−2 | OK | R-CL · S-RAIL · H-TABLE · F-LEAD (brief) |
| Day brief + anti-patrones | OK | docs/00-day-brief.md (no sobrescrito) |
| Paper ≥12 UX + ≥10 UI | OK | UX-count 12 · UI-count 11 · file 01M04P0VFWGX1DQVSCR5TPWM9Z |
| Docs 00–20 | OK | suite en docs/ + executive + README |
| JWT STAFF | OK | Role STAFF · D-P1-05 |
| API + seed + Neon | OK | contratos jolly-night-99671179, port 3016 |
| Web tokens | OK | Spectral + Manrope · paleta botanical |
| Table-first (no AP-12) | OK | UI-01 / docs 03, 07, 08 |
| Hipótesis no fake field | OK | etiquetas en doc 02 |
| Copy compromiso ≠ entrega | OK | docs 01, 09, 16 |
3. Cobertura funcional
| Feature brief | Spec | UI Paper | API | Docs |
|---|---|---|---|---|
| Home tabla 5 necesidades | Sí | UI-01, UI-10 | GET needs | 03, 04, 16 |
| Ficha + foto | Sí | UI-02 | GET :slug | 05, 09 |
| Form compromiso | Sí | UI-03 | POST requests | 04, 05 |
| Success + CE- | Sí | UI-04 | code · /ok/:code | 04, 09 |
| Login STAFF | Sí | UI-05 | POST login | 04, 06 |
| Inbox 5 estados | Sí | UI-06 | GET + PATCH | 04, 05 |
| Empty | Sí | UI-07 | 200 [] | 16 |
| Error | Sí | UI-08 | 5xx/red | 16 |
| Loading | Sí | UI-09 | pending | 16 |
| Design system | Sí | UI-00 | — | 08 |
| Seed 5 needs | Sí | — | seed | 05 |
| Rail | Sí | UI-01, UI-10 | — | 03, 08 |
4. Cobertura Paper (literales del brief)
| ID | Nombre | En 00-paper-reference |
|---|---|---|
| UX-00 | Cover | Sí |
| UX-01 | Stakeholders | Sí |
| UX-02 | Personas | Sí |
| UX-03 | JTBD | Sí |
| UX-04 | Stories | Sí |
| UX-05 | Journey | Sí |
| UX-06 | Blueprint | Sí |
| UX-07 | Site map | Sí |
| UX-08 | Flujos | Sí |
| UX-09 | Datos+permisos | Sí |
| UX-10 | Métricas | Sí |
| UX-11 | Research | Sí |
| UI-00 | Tokens | Sí |
| UI-01 | Tabla | Sí |
| UI-02 | Ficha | Sí |
| UI-03 | Compromiso | Sí |
| UI-04 | Success | Sí |
| UI-05 | Login | Sí |
| UI-06 | Inbox | Sí |
| UI-07 | Empty | Sí |
| UI-08 | Error | Sí |
| UI-09 | Loading | Sí |
| UI-10 | Mobile tabla | Sí |
[COMPROBADO] Nombres coinciden con el encargo. UX-count: 12 y UI-count: 11 literales para el gate check-paper-reference.mjs.
5. Rúbrica de calidad (auto SCORE orientativo · docs)
| Eje | Score 1–5 | Comentario |
|---|---|---|
| Diversidad | 5 | Tabla+rail vs feed RONDA y mapa DERIVA; L1 no L2 |
| Craft visual (spec) | 4–5 | Botanical + Spectral/Manrope + fotos ítem documentadas |
| Densidad UX docs | 5 | Suite L1 alto: tablas, AC, etiquetas |
| Completitud código L1 | — | App fuera o en paralelo; leftover scaffold anotado |
| Authz | 5 | Contrato: guard en list/patch |
| Verdad investigación | 5 | Sin entrevistas ni stats de campo falsas |
6. Huecos aceptados (no regresiones de cierre documental)
| Hueco | Clasificación |
|---|---|
| Pagos / feed / mapa / search-home | Fuera L1 |
| e2e automatizado | L1+ |
| Route guards formales Angular | L1+ |
| Check DB hueco / ajuste PARTIAL | L1+ / L2 |
| Checkbox RGPD | L1+ documentado |
Leftover /mesas en repo | Build; contrato de esta suite gana |
| Paper/app no necesariamente tocados aquí | Encargo = documentación |
7. Veredicto
COMPLETO para entrega documental del caso 2026-08-16.
La suite define producto L1 usable (tablas, AC, riesgos, contratos) sin presentar investigación de campo como hecho.
Cualquier ampliación mapa / search-first / pagos / feed requiere nuevo brief de diversidad, no parche silencioso.
19-backlog-completo.md
19 — Backlog completo — CESTA
Inventario de ítems. Los del alcance L1 documental del día están Done.
El resto es opcional / siguiente nivel, no deuda oculta del cierre.
1. Done — L1 case documental (2026-08-16)
| ID | Ítem | Capa |
|---|---|---|
| D01 | Definición producto CESTA + eslogan | Docs |
| D02 | Day brief diversidad ONG L1 | Docs (preexistente, no pisado) |
| D03 | Terna R-CL · S-RAIL · H-TABLE · F-LEAD | Docs |
| D04 | Personas Rosa Belmonte / Inés Marzal | Docs + Paper ref |
| D05 | JTBD + stories Must | Docs |
| D06 | IA table-first + rail | Docs |
| D07 | Flujos F1–F8 + AC | Docs |
| D08 | Modelo users / needs / requests | Docs |
| D09 | Contratos API :3016 | Docs |
| D10 | Auth JWT STAFF | Docs |
| D11 | 5 estados incl. RECEIVED y PARTIAL | Docs |
| D12 | Código CE-MMDD-NNN | Docs |
| D13 | Disclaimer compromiso ≠ entrega | Docs |
| D14 | North star CONTACTED antes de franja | Docs |
| D15 | Tokens botanical + Spectral / Manrope | Docs |
| D16 | Paper UX-00…11 + UI-00…10 | Docs |
| D17 | Suite docs 00–20 + README + executive | Docs |
| D18 | Seed 5 necesidades + solicitudes especificado | Docs |
| D19 | Assets listados | README |
| D20 | QA smoke + S1–S22 | Docs |
| D21 | Neon jolly-night-99671179 | Docs |
| D22 | Rutas reales solo las 6 del encargo | Docs |
2. Backlog L1 implementación (repo cesta-app)
| ID | Ítem | Prioridad | Notas |
|---|---|---|---|
| I01 | Alinear apps/api + apps/web al contrato CESTA | P0 | quitar leftover mesas/tables |
| I02 | Schema needs / requests / users | P0 | |
| I03 | Seed Inés + 5 needs + requests | P0 | |
| I04 | GET needs / :slug | P0 | |
| I05 | POST requests + código CE- | P0 | body needSlug, name, email, quantity, note |
| I06 | POST login JWT | P0 | ines@cesta.barrio |
| I07 | GET requests + PATCH :id { status } | P0 | |
| I08 | Tabla + rail Angular | P0 | |
| I09 | Ficha / comprometer / ok / login / inbox | P0 | |
| I10 | Tokens Tailwind + fotos | P0 | |
| I11 | Smoke curl + build | P0 | D-P1-06 |
| I12 | Puerto API 3016 | P0 |
3. Backlog L1+ (polish)
| ID | Ítem | Prioridad | Notas |
|---|---|---|---|
| B01 | canActivate guards Angular | P1 | UX auth |
| B02 | Filtros por status en inbox | P2 | Query API |
| B03 | Ajuste quantity en PARTIAL | P1 | Q5 |
| B04 | Captcha / rate limit POST | P1 | anti-spam |
| B05 | Checkbox privacidad | P1 | RGPD |
| B06 | Playwright smoke | P1 | CI |
| B07 | Skeleton UI-09 en código | P2 | |
| B08 | mailto en inbox | P2 | ops |
| B09 | Copy code clipboard | P3 | |
| B10 | API URL por environment | P1 | deploy |
| B11 | Página 404 amigable | P3 | |
| B12 | Sessions invalidables | P2 |
4. Backlog L2 / L3 (requiere brief nuevo)
| ID | Ítem | Dependencia |
|---|---|---|
| C01 | Multi-sede | venues + membership |
| C02 | Cuenta VECINO | authz |
| C03 | Email / WhatsApp transaccional | provider |
| C04 | Lookup público por código | authz |
| C05 | Check DB hueco | transacción |
| C06 | Audit log | Event |
| C07 | CMS Need | Role EDITOR |
| C08 | Inventario ERP | lotes |
5. Backlog explícitamente fuera
| ID | Ítem |
|---|---|
| E01 | Pagos / Bizum / socio / certificado fiscal |
| E02 | Feed de impacto |
| E03 | Mapa de despensas |
| E04 | Search-first como home |
| E05 | Kanban de compromisos |
| E06 | CRM de donantes |
| E07 | App nativa |
6. Explicitamente no-backlog
| Idea | Razón |
|---|---|
| Hero 2-col + 3 cards | AP-12 / rompe H-TABLE |
| Sidebar CRM | S-SIDE |
| Dock flotante | S-DOCK de RONDA |
| Command search municipal | PIZARRA |
| Split mapa | DERIVA |
| Wizard donación 4 pasos | F-ONB |
7. Orden de ataque recomendado (continuidad de código)
- I01–I12 (vertical slice runnable, sin leftover)
- B01 + B06 + B10
- B03 + B08
- B04 + B05 si hay tráfico
- Evaluar brief L2 — no parche silencioso
8. Trazabilidad
| Origen | Ítems |
|---|---|
| Day brief must-have | D01–D22, I01–I12 |
| Doc 15 L1+ | B01–B12 |
| Doc 15 L2 / fuera | C01–C08, E01–E07 |
20-implementation.md
20 — Implementación — CESTA
1. Resumen ejecutivo técnico
| Campo | Valor |
|---|---|
| App path | /Users/cristian/orca/cesta-app |
| API | NestJS · puerto 3016 · prefijo /api |
| Web | Angular standalone · puerto 4200 |
| Package | apps independientes (npm --prefix o pnpm --filter) |
| DB | Neon PostgreSQL · project jolly-night-99671179 |
| Tablas | users, needs, requests (+ sessions Should) |
| Auth | JWT Bearer · role STAFF |
| Dominio | User, Need, Request |
| Fecha | 2026-08-16 |
| GitHub | https://github.com/Criscode2022/cesta-app |
Este documento es la especificación de build alineada al case. No sustituye al código: si el repo diverge, gana el contrato de docs 05 + este archivo tras actualizar ambos.
[COMPROBADO] Path, puerto, Neon, Paper y GitHub salen del encargo.
[COMPROBADO] El repo existe; puede contener leftover de otro producto (/mesas, GET /tables). El contrato de esta suite es CESTA.
2. Cómo arrancar
cd /Users/cristian/orca/cesta-app
# instalar dependencias por app (no npm workspaces)
# apps/api/.env
# DATABASE_URL= # Neon jolly-night-99671179
# JWT_SECRET=
# PORT=3016
# schema + seed
# api → http://localhost:3016
# web → http://localhost:4200
Credenciales
| Rol | Password | |
|---|---|---|
| STAFF | ines@cesta.barrio | password123 |
3. Schema
Al boot o vía migrate:
CREATE TABLE/ migrate deusers,needs,requests(doc 05).- Índices:
needs(slug),requests(created_at DESC),requests(need_id, status),users(email). - Seed idempotente:
- upsert STAFF
ines@cesta.barrio(bcrypt depassword123) - upsert 5
needsporslug - upsert
requestsporcode
- upsert STAFF
No borrar datos de usuario en cada boot si ya existen filas distintas al seed.
Tablas (recordatorio)
| Tabla | Contenido |
|---|---|
users | STAFF Inés |
needs | 5 líneas seed |
requests | Compromisos públicos |
sessions | Should: sid + token_hash + expires_at |
4. Módulos API a implementar
Auth
POST /api/auth/login- Valida email/password; compara bcrypt; emite JWT con
sub, email, role. - Guard JWT protege lectura/escritura de requests.
Needs (público)
| Método | Ruta | Auth | Notas |
|---|---|---|---|
| GET | /api/needs | No | incluye committedQty, gap |
| GET | /api/needs/:slug | No | 404 si no existe |
Requests
| Método | Ruta | Auth | Notas |
|---|---|---|---|
| POST | /api/requests | No | body { needSlug, name, email, quantity, note }; status NEW; code CE- |
| GET | /api/requests | JWT | list + need; createdAt desc |
| PATCH | /api/requests/:id | JWT | body { status } |
Generación de código
CE- + MMDD (Europe/Madrid) + - + NNN
NNN = siguiente entero del día, padded 3. Si unique falla, reintentar NNN+1 (máx. 5).
[DECISIÓN] Timezone Europe/Madrid, no UTC, para que el código coincida con el mostrador.
Agregados
committedQty = SUM(quantity) WHERE status <> 'CANCELLED'
gap = GREATEST(0, requestedQty - committedQty)
RECEIVED / PARTIAL no actualizan requested_qty.
5. Frontend a implementar
| Página | Ruta | Responsabilidad |
|---|---|---|
| HomePage | / | GET needs, tabla, empty/loading/error, rail |
| NeedPage | /necesidades/:slug | GET slug, foto, disclaimer, CTA |
| CommitPage | /necesidades/:slug/comprometer | form create público + disclaimer |
| OkPage | /ok/:code | confirmación + código + “no es entrega” |
| LoginPage | /login | form → login → inbox |
| InboxPage | /inbox | list + PATCH en fila + empty/error + logout |
Shell: RailComponent persistente.
ApiService centraliza base URL http://localhost:3016/api, token storage (cesta_token, cesta_user), métodos tipados (Need, Request, User).
6. Decisiones de implementación
| Decisión | Razón |
|---|---|
| Puerto API 3016 | Evitar colisión con RONDA 3015 |
Tablas needs / requests | Dominio despensa, no leftover tables |
| Soft auth en páginas | Simple; API es autoridad |
| Sin FK User–Request | Form anónimo |
| Código CE-MMDD-NNN | Referencia oral corta |
| Templates standalone | Velocidad daily |
| No bloquear POST por hueco 0 | Q4; STAFF decide |
| Home no usa hero como layout | AP-12 / H-TABLE |
| RECEIVED no ENROLLED / SEATED | Dominio de caja, no municipal ni mesa |
| JSON camelCase | Convenio web serie daily |
PATCH :id no :id/status | Contrato del encargo |
Success /ok/:code | Contrato del encargo |
7. Variables de entorno
| Variable | Servicio | Descripción |
|---|---|---|
DATABASE_URL | API | Neon jolly-night-99671179 |
JWT_SECRET | API | Firma tokens |
PORT | API | opcional, 3016 |
JWT_EXPIRES | API | opcional, default 12h |
Web: URL de API en ApiService (default localhost:3016).
8. Smoke de implementación (mínimo)
- Schema OK (tablas existen).
GET /api/needslength ≥ 5, incluyearroz,aceite,tomate,panales,leche.POST /api/requests201 +codeCE-.POST /api/auth/login200 +accessToken.GET /api/requestsBearer incluye el POST.GET /api/requestssin token 401.PATCH /api/requests/:id{ status: "RECEIVED" }200 yrequestedQtyintacto.- Web: tabla dominante, rail, disclaimer, fotos.
9. Estructura de ficheros clave (objetivo)
cesta-app/
├── package.json
├── apps/api/
│ ├── src/
│ │ ├── main.ts
│ │ ├── app.module.ts
│ │ ├── auth/
│ │ ├── needs/
│ │ └── requests/
│ └── .env.example
├── apps/web/
│ ├── tailwind.config.js
│ └── src/app/
│ ├── app.routes.ts
│ ├── core/api.service.ts
│ ├── shell/rail.component.ts
│ └── pages/
│ ├── home/ # tabla
│ ├── need/
│ ├── commit/
│ ├── ok/
│ ├── login/
│ └── inbox/
└── README.md
Nombres de fichero orientativos; el contrato es de rutas y tablas, no de filenames exactos.
10. Seed — checklist de implementación
| # | Check |
|---|---|
| 1 | User Inés Marzal / ines@cesta.barrio / password bcrypt de password123 |
| 2 | 5 needs con slugs de doc 05 y image_key alineado a assets |
| 3 | Unidades kg / L / ud / packs y pedidos 40 / 24 / 36 / 12 / 48 |
| 4 | Requests CE-0816-001…005 cubriendo NEW, CONTACTED, RECEIVED, PARTIAL, CANCELLED |
| 5 | Rosa → aceite 3 L NEW |
| 6 | Youssef → tomate PARTIAL |
| 7 | Idempotencia: upsert por email/slug/code |
11. Alineación case ↔ app
| Doc | Evidencia esperada en código |
|---|---|
| 05 data model | columnas y enums |
| 03–04 IA/flows | routes + controllers |
| 08 DS | tailwind colors + fontFamily Spectral/Manrope |
| 09 content | strings en templates (disclaimer ×3) |
| 00 paper | URLs en README case |
| 11 security | Guard en GET/PATCH requests |
12. Notas de cierre técnico
- El case documental L1 está especificado (docs + Paper ref + contratos).
- Must-have de producto del brief están escritos con AC.
- Mejoras (guards Angular, e2e, rate limit) viven en backlog L1+, no como deuda silenciosa.
- Si el repo aún muestra feed de mesas, no está alineado: I01 es el primer ítem de build.