00-day-brief.md
00 — Day brief · 2026-08-13 · PIZARRA
Decisiones ALS-2 BRIEF
| Campo | Valor |
|---|---|
| Fecha | 2026-08-13 |
| Nombre | PIZARRA |
| Complejidad | Nivel 2 (no L3 consecutivo tras PRIMA) |
| Sector | Educación cívica / talleres municipales |
| Tipo | Catálogo público de talleres + solicitud de plaza + inbox COORD JWT |
| Plataforma | Web responsive (search-first vecino + desktop coordinación) |
| Registro | R-CL — clásico institucional excelente (aula municipal, no SaaS) |
| Shell | S-CMD — command palette / search-first |
| Home | H-SEARCH — la búsqueda es la home |
| Flujo | F-SEARCH — query + facetas → ficha → solicitud |
| Por qué no PRIMA | No S-SIDE / H-DATA / F-KAN; no seguros; no kanban |
| Por qué no CANTERA | No S-BOTTOM / H-WIZARD / F-ONB; no construcción |
Terna unicidad
R-CL · S-CMD · H-SEARCH · F-SEARCH
Cumple memory §6.5: ≥2 códigos distintos vs N−1 (PRIMA) y vs N−2 (CANTERA). Ventana de 4 días recupera R-CL (faltaba tras PRIMA híbrido).
Directivas aplicadas
- D-P0-01…08, D-P1-01 (≥12 UX Paper), D-P0-08 (≥10 UI), D-P1-05 JWT COORD
- Tipografía Newsreader + Atkinson Hyperlegible (evitar Outfit, Archivo, Fraunces, Syne)
- Paleta chalky — tiza / pizarra / musgo / rail — no slate/teal, no limestone, no wine
- Home no es hero 2-col + 3 cards (AP-12). La superficie principal es la barra de búsqueda + resultados facetados.
Alcance L2 must
- Home search-first: query + chips de disciplina / distrito / precio / accesibilidad
- Ficha de taller con foto real, aforo, horario, sede
- Formulario público de solicitud (solicitud ≠ plaza confirmada)
- Success con código
PZ-MMDD-NNN - Login + inbox COORD JWT (estados NEW → CONTACTED → ENROLLED / WAITLIST / CANCELLED)
- Empty / loading / error / mobile search
- ≥12 UX + ≥10 UI artboards Paper
Supuestos documentados
- S1: Un distrito tipo Usera–Vallecas publica 8–20 talleres por trimestre (rango operativo, no dato de campo).
- S2: El vecino llega con una pregunta (“cerámica los martes cerca”) no a “explorar marca”.
- S3: La coordinación municipal necesita cola con estado, no un LMS ni pagos online.
- S4: Cupo visible reduce llamadas; la inscripción definitiva sigue siendo administrativa.
Hipótesis
| ID | Hipótesis | Señal |
|---|---|---|
| H1 | Search + facetas reduce abandono vs listado PDF/web institucional | % sesiones con ≥1 facet + click ficha |
| H2 | Copy honesto “solicitud, no plaza” baja expectativas rotas | ↓ reclamaciones “pensé que estaba inscrito” |
| H3 | Inbox con WAITLIST evita overbooking informal | ENROLLED nunca supera seats |
Anti-patrones evitados
- AP-12 plantilla VOLTA/RELE (hero 2-col + lista badges)
- AP-16 UI thin (se planifican 12 UI)
- AP-08 UX thin (12 boards canónicos)
- No L3, no kanban, no wizard campo
Mood visual (Paper)
- Candidatos: chalky · chapel · bookish · botanical · mineral
- Elegido: chalky (no el primer instinto “bookish biblioteca”) — la metáfora es la pizarra del aula, no el folio.
- Paleta:
#E4EAF2polvo ·#1A231Epizarra ·#D4A017tiza ·#3F6B5Aborrador ·#D7D0C2rail ·#EEF2F8papel - Tipo: Newsreader 56/40/28 display · Atkinson 16/14 UI
00-paper-reference.md
Paper reference · PIZARRA
| URL | https://app.paper.design/file/01KZWYWE6BFRD96MAAF8HQWJ78 |
| File ID | 01KZWYWE6BFRD96MAAF8HQWJ78 |
UX-count: 12 UI-count: 12
UX process (prefijo UX- · mínimo 12)
| ID | Nombre | Contenido |
|---|---|---|
| UX-00 | Cover | Portada PIZARRA, terna R-CL·S-CMD·H-SEARCH·F-SEARCH, fecha 2026-08-13, L2 cívico, chalky |
| UX-01 | Stakeholders | Vecindad, COORD, centros cívicos, concejalía, monitorado, familia |
| UX-02 | Personas | Rosa Belmonte (vecina 54) · Inés Marzal (COORD 38) |
| UX-03 | JTBD | Job “encontrar taller y pedir plaza” + job “contactar solicitudes en 48 h” |
| UX-04 | Stories | Must: search, ficha, solicitud, código PZ-, inbox, estados, empty/error |
| UX-05 | Journey | Query → facetas → ficha → solicitud → espera → contacto COORD |
| UX-06 | Blueprint | Frontstage search/form · backstage llamada · sistemas Nest/Neon |
| UX-07 | Site map | Público search-first + auth COORD; profundidad 2 |
| UX-08 | Flujos | F-SEARCH: query+chips → ficha → POST → success; F-COORD inbox |
| UX-09 | Datos + permisos | Workshop · SeatRequest · User COORD; JWT en list/patch |
| UX-10 | Métricas | North star % válidas CONTACTED ≤ 48 h; facetas; abandono |
| UX-11 | Research notes | Comprobado / supuesto / hipótesis / decisión — sin fake field |
UI producto (prefijo UI- · mínimo 10)
| ID | Nombre | Flujo |
|---|---|---|
| UI-01 | Home search | H-SEARCH: query + chips disciplina/distrito/precio/a11y + resultados |
| UI-02 | Taller detalle | Ficha foto real, aforo, horario, sede, CTA solicitar |
| UI-03 | Solicitar plaza | Form público; disclaimer solicitud ≠ plaza confirmada |
| UI-04 | Success | Acuse + código PZ-MMDD-NNN + próximos pasos |
| UI-05 | Login COORD | Acceso JWT al inbox de coordinación |
| UI-06 | Inbox COORD | Lista + stats NEW/CONTACTED/ENROLLED/WAITLIST/CANCELLED |
| UI-07 | Detalle solicitud | Contacto, taller, aforo vs demanda, acciones de estado |
| UI-08 | Empty search | Cero resultados; sugerir quitar faceta / otra query |
| UI-09 | Error | Fallo de red / API; reintentar |
| UI-10 | Loading | Skeleton resultados / inbox |
| UI-11 | Design system | Tokens chalky, tipo Newsreader + Atkinson, badges, search field |
| UI-12 | Mobile search | Home search ~390px; chips wrap; targets ≥44px |
Regla: wizard de N pasos → N boards UI solo para esos pasos, además de success/empty/error/loading/staff. PIZARRA no es wizard: el núcleo es search → ficha → form de 1 pantalla.
1. Mapa de canvas (bandas)
| § | Banda | Propósito | Artboards clave |
|---|---|---|---|
| 1 | UX PROCESS | Modelo de servicio cívico | UX-00…UX-11 |
| 2 | DESIGN SYSTEM | Tokens chalky + componentes search | UI-11 |
| 3 | PUBLIC SEARCH | Cara vecina search-first | UI-01, UI-02, UI-03, UI-04, UI-08, UI-12 |
| 4 | COORD | Flujos autenticados | UI-05, UI-06, UI-07 |
| 5 | STATES | Resiliencia | UI-09, UI-10 |
Layout canvas (referencia de diseño): origen (0,0) · gaps ~100–120px · UX en 3×4 · UI pública en fila search → ficha → form → success.
2. Mapeo Paper → Angular
| Artboard | Ruta app | Componente |
|---|---|---|
| UI-01 Home search | / | SearchPage |
| UI-02 Taller detalle | /taller/:slug | WorkshopPage |
| UI-03 Solicitar plaza | /solicitar/:slug | RequestPage |
| UI-04 Success | /ok | SuccessPage |
| UI-05 Login COORD | /login | LoginPage |
| UI-06 Inbox COORD | /inbox | InboxPage |
| UI-07 Detalle solicitud | /inbox/:id | RequestDetailPage |
| UI-08 Empty search | /?q=… 0 hits | empty en home |
| UI-09 Error | search / inbox | banner error + retry |
| UI-10 Loading | search / inbox | skeletons |
| UI-11 Design system | — | tokens Tailwind |
| UI-12 Mobile search | / viewport 390 | mismo HomeSearchPage |
3. Tokens de diseño en Paper
| Token | Valor | Uso |
|---|---|---|
| Polvo / bg | #E4EAF2 | Fondo de página |
| Papel | #EEF2F8 | Surfaces, cards |
| Pizarra | #1A231E | Texto fuerte, CTAs dark |
| Tiza | #D4A017 | Acento search, focus, highlights |
| Musgo | #3F6B5A | Acciones positivas, ENROLLED |
| Rail | #D7D0C2 | Bordes, chips inactivos |
| Display | Newsreader 56/40/28 | Titulares, wordmark |
| UI | Atkinson Hyperlegible 16/14 | Body, labels, search, forms |
4. Checklist de densidad (anti thin-frames)
| Criterio | UX | DS | Public search | COORD | States |
|---|---|---|---|---|---|
| Jerarquía tipográfica visible | Sí | Sí | Sí | Sí | Sí |
| Microcopy real (no lorem) | Sí | Sí | Sí | Sí | Sí |
| Tokens chalky aplicados | — | Sí | Sí | Sí | Sí |
| Datos de seed creíbles | Personas | — | 6 talleres + fotos | Códigos PZ- | Empty realista |
| Media / iconografía | Cover | — | Fotos taller | Badges estado | Skeletons |
5. Media
assets/hero.jpgassets/taller-ceramica.jpgassets/taller-escritura.jpgassets/taller-teatro.jpgassets/taller-cocina.jpgassets/taller-robotica.jpgassets/taller-musica.jpg
6. Enlaces
- Paper: https://app.paper.design/file/01KZWYWE6BFRD96MAAF8HQWJ78
- Case:
/Users/cristian/orca/ux-projects/2026-08-13-pizarra/ - App:
/Users/cristian/orca/pizarra-app/ - GitHub: https://github.com/Criscode2022/pizarra-app
- Neon project:
crimson-voice-36926751
01-project-definition.md
01 — Definición de proyecto — PIZARRA
1. Identidad
| Campo | Valor |
|---|---|
| Nombre | PIZARRA |
| Significado | La pizarra del aula municipal: oferta visible, tiza, cupo y nombre |
| Eslogan | ”La plaza se pide con nombre, no con un PDF.” |
| Una frase | Catálogo search-first de talleres municipales + solicitud pública de plaza + inbox JWT de coordinación. |
| Sector | Educación cívica / talleres municipales |
| Tipo | Web cívica search-first + SeatRequest + inbox COORD — Nivel 2 |
| Plataforma | Web responsive (móvil-first vecino + desktop coordinación) |
| Mercado demo | España · Madrid · distritos Usera, Vallecas, Latina, Carabanchel, Villaverde, Arganzuela |
| Idioma | es-ES |
| Fecha caso | 2026-08-13 |
| Terna | R-CL · S-CMD · H-SEARCH · F-SEARCH |
2. Problema
Principal (hipótesis de diseño)
[HIPÓTESIS] La vecindad descubre talleres en carteles, PDFs trimestrales y listados institucionales difíciles de filtrar. Llega con una pregunta (“cerámica los martes cerca”), no a explorar marca. [SUPUESTO] La coordinación municipal recibe llamadas y correos sin cola, sin código de referencia y sin estado compartido.
Secundarios
| Problema | Quién lo sufre | Efecto |
|---|---|---|
| Oferta enterrada en PDF / mega-listado | Vecina (Rosa) | Abandono antes de encontrar el taller |
| “¿Quedan plazas?” por teléfono | Ambos | Cola informal, overbooking |
| Mensaje enviado sin acuse | Vecina | No sabe si llegó; llama de nuevo |
| Excel / correo sin estado | COORD (Inés) | Se pierde quién ya fue contactado |
| Expectativa de “ya estoy inscrita” | Ambos | Reclamaciones y cupo fantasma |
Supuestos (no investigación primaria propia)
- S1: Un distrito tipo Usera–Vallecas publica 8–20 talleres por trimestre (rango operativo, no dato de campo).
- S2: El vecino llega con una pregunta concreta, no a “explorar marca”.
- S3: La coordinación necesita cola con estado, no un LMS ni pagos online.
- S4: El cupo visible reduce llamadas; la inscripción definitiva sigue siendo administrativa.
Hipótesis de producto
| ID | Hipótesis | Señal de validación (futura) |
|---|---|---|
| H1 | Search + facetas reduce abandono vs listado PDF/web institucional | % sesiones con ≥1 facet + click ficha |
| H2 | Copy honesto “solicitud, no plaza” baja expectativas rotas | ↓ reclamaciones “pensé que estaba inscrito” |
| H3 | Inbox con WAITLIST evita overbooking informal | ENROLLED nunca supera seats |
No se afirman estadísticas de demanda municipal inventadas. Todo lo anterior es razonamiento de diseño etiquetado.
3. Propuesta de valor
| Para | Valor |
|---|---|
| Vecina (Rosa Belmonte) | Encuentra un taller por disciplina, distrito y accesibilidad; pide plaza en un minuto; se lleva un código. |
| COORD (Inés Marzal) | Ve solicitudes nuevas, marca contacto en ≤ 48 h, inscribe o pone en lista de espera sin Excel. |
| Centro cívico / concejalía | Canal digital mínimo: oferta visible + pipeline de contacto, sin portal de matrícula completo. |
No es PIZARRA
| Excluido | Por qué |
|---|---|
| LMS / aula virtual / entregas | Job distinto (enseñar, no asignar plaza) |
| Pagos, tasa online, pasarela | Inscripción administrativa; tasa informativa |
| Marketplace de monitores privados | Oferta municipal, no matching |
| CRM multi-concejalía / CITA previa sanitaria | Sobre-arquitectura L3+ |
| Hero 2-col + 3 cards de “programa estrella” | Anti-patrón AP-12; contradice H-SEARCH |
4. Objetivos
Negocio / caso de estudio
- Demostrar vertical slice L2 cívico search-first con catálogo Workshop + SeatRequest + JWT COORD.
- Portfolio coherente: Paper (12 UX + 12 UI) + docs + app runnable.
- Recuperar registro R-CL y shell S-CMD frente a PRIMA (S-SIDE / H-DATA / F-KAN).
Usuario
| Rol | Objetivo medible en demo |
|---|---|
| Vecina | Encontrar un taller en < 60 s (query o chip) y enviar solicitud en < 2 min |
| COORD | Marcar CONTACTED / ENROLLED / WAITLIST en < 3 taps desde el inbox |
No objetivos v1 (explícitos)
- Pago de tasas
- Cuenta de vecino / “mis solicitudes”
- Aforo en tiempo real multi-sede sincronizado con mostrador
- Chat in-app o notificación email transaccional
- CMS de talleres para editores de distrito
- Calendario iCal / recordatorios
5. Roles y permisos (resumen)
| Acción | Público | COORD |
|---|---|---|
| Buscar / filtrar talleres | Sí | Sí |
| Ver ficha de taller | Sí | Sí |
| POST solicitud | Sí | Sí (mismo form) |
| Login JWT | No (no cuenta vecina) | Sí |
| Listar solicitudes | No (401) | Sí |
| Ver stats summary | No | Sí |
| Ver detalle solicitud | No | Sí |
| Cambiar status | No | Sí |
| Crear / editar Workshop | No (seed) | No en v1 |
6. Métricas (modelo, no instrumentadas en v1 salvo base)
| Tipo | Métrica | Definición |
|---|---|---|
| North Star | % solicitudes válidas CONTACTED ≤ 48 h | Calidad operativa del contacto, no “matrículas vendidas” |
| Activación | 1ª solicitud pública | POST create 2xx |
| Search | % sesiones con ≥1 faceta + click ficha | H1 |
| Expectativa | Reclamaciones “ya inscrito” | H2 (cualitativa / tickets) |
| Integridad | ENROLLED ≤ seats por taller | H3 |
| Salud | Error rate API search/create/list | 4xx/5xx |
7. Alcance funcional v1 (L2)
| Módulo | Incluido |
|---|---|
| Home search | Query + chips disciplina / distrito / precio / accesibilidad + resultados |
| Catálogo | 6 talleres seed con foto real, aforo, horario, sede |
| Ficha | Detalle + CTA solicitar + disclaimer |
| Solicitud | Form + POST /api/requests |
| Success | Código PZ-MMDD-NNN + próximos pasos |
| Auth | POST /api/auth/login → JWT COORD |
| Inbox | GET list, GET stats, GET by id, PATCH status |
| Estados UI | Empty search, loading skeleton, error de red |
| Mobile search | Misma home a ~390px |
| Seed | 1 COORD, 6 workshops, 6 seat requests |
8. Criterios de aceptación de producto
- Un visitante puede buscar y filtrar talleres sin autenticación y abrir una ficha.
- Un visitante puede enviar una solicitud sin cuenta y recibir un código
PZ-…. - La UI dice explícitamente que la solicitud no confirma plaza.
- Sin token, GET/PATCH
/api/requests*responden 401. - GET
/api/workshopsacepta queryqy facetas (discipline,district,price,accessibility). - Un COORD puede iniciar sesión y ver el inbox ordenado (
createdAtdesc) con stats. - El detalle permite transicionar
NEW | CONTACTED | ENROLLED | WAITLIST | CANCELLED. - La home es la búsqueda: no hay hero 2-col + 3 cards como superficie principal.
- Distritos cubiertos en seed: Usera, Vallecas, Latina, Carabanchel, Villaverde, Arganzuela.
- Fotos de taller reales en fichas (assets del case).
9. Stack y artefactos
| Capa | Detalle |
|---|---|
| Frontend | Angular + Tailwind · puerto 4200 |
| Backend | NestJS · puerto 3013 |
| DB | Neon PostgreSQL · Prisma · project crimson-voice-36926751 |
| Auth | JWT (passport/strategy en API) |
| Diseño | Paper 01KZWYWE6BFRD96MAAF8HQWJ78 |
| Repo app | /Users/cristian/orca/pizarra-app/ · GitHub Criscode2022/pizarra-app |
10. Riesgos y mitigaciones
| Riesgo | Impacto | Mitigación v1 |
|---|---|---|
| Spam en form público | Inbox ruidoso | Validación server; honeypot / rate-limit en L2+ |
| Expectativa de matrícula instantánea | Reclamación y cupo fantasma | Copy “solicitud ≠ plaza”; estado ENROLLED solo COORD |
| Overbooking | Conflicto en aula | WAITLIST + regla H3 documentada; COORD no debe ENROLLED > seats |
| Confundir con “cursos online” | Expectativa LMS | Copy presencial, sede, horario de aula |
| PII de vecindad en solicitudes | Privacidad municipal | Solo COORD lista; doc 11 |
| Search vacío frustrante | Abandono | Empty state con “quita un filtro” + chips populares |
| Home percibida como portal institucional genérico | Pérdida de craft | Chalky + Newsreader; search como superficie |
11. Glosario
| Término | Definición en PIZARRA |
|---|---|
| Taller | Workshop publicado: disciplina, distrito, sede, horario, aforo |
| Solicitud | SeatRequest; no es plaza confirmada |
| Plaza / inscripción | Solo cuando COORD marca ENROLLED |
| Lista de espera | WAITLIST cuando seats están cubiertos o hay duda de cupo |
| COORD | Usuario autenticado de coordinación de distrito / programa |
| Código PZ- | Identificador corto oral (PZ-0813-001) |
| Faceta | Filtro de catálogo: disciplina, distrito, precio, accesibilidad |
| Tasa | Precio informativo (gratuito o importe); no cobro in-app |
12. Decisiones de diseño (cierre de brief)
| ID | Decisión | Alternativa descartada |
|---|---|---|
| D1 | Home = search (H-SEARCH) | Hero 2-col + 3 cards (AP-12) |
| D2 | Shell command/search (S-CMD) | Sidebar CRM o bottom-nav wizard |
| D3 | Registro clásico institucional (R-CL) | SaaS slate/teal o “wellness” |
| D4 | Flujo F-SEARCH | Wizard de onboarding o kanban |
| D5 | Mood chalky (pizarra de aula) | Bookish biblioteca (primer instinto) |
| D6 | Un rol autenticado COORD | Multi-rol monitor / concejalía |
| D7 | Catálogo Workshop de 1ª clase | Enum de tipos como en L1 fitness |
02-ux-research-strategy.md
02 — Estrategia de investigación UX — PIZARRA
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 DE DISEÑO].
1. Objetivos de investigación (del caso)
| Objetivo | Método en este caso | Salida |
|---|---|---|
| Entender actores del programa municipal | Modelado de stakeholders + personas | §3–4 |
| Definir job de búsqueda y de contacto | JTBD + stories Must | §5 |
| Mapear fricción PDF → plaza | Journey + service blueprint | §6–7 |
| Traducir a requisitos L2 search-first | Matriz hallazgo → requisito → feature | §8 |
2. Fuentes y límites
Fuentes admisibles (secundarias / operativas)
- Conocimiento general de centros cívicos, programaciones trimestrales y colas de inscripción presencial.
- Analogía operativa con catálogos search-first + inbox JWT de la serie daily.
- Restricciones ALS-2: sector cívico libre; no LMS; no kanban; no wizard de campo.
Límites éticos de verdad
| Prohibido | Permitido |
|---|---|
| “El 72% de vecinas abandona el PDF del distrito” sin fuente | “[SUPUESTO] el vecino llega con una query, no a explorar marca” |
| Citas de entrevistas ficticias como campo real | Quotes de persona etiquetadas como constructo de diseño |
| SLA medido “48 h en Usera” como KPI real | North star modelo “CONTACTED ≤ 48 h” |
| Aforo real de un centro concreto | Aforos de seed de producto |
3. Stakeholders
| Stakeholder | Influencia | Interés | Necesidad principal |
|---|---|---|---|
| Vecina / vecino participante | Baja formal | Muy alta | Encontrar taller y dejar solicitud con acuse |
| COORD de programa (Inés) | Alta | Muy alta | Cola con estado y código oral |
| Familia / acompañante | Media | Alta | Horario, sede, accesibilidad claros |
| Monitor / tallerista | Media | Media | Grupo cerrado a tiempo, sin overbooking |
| Dirección del centro cívico | Alta adopción | Media | Menos mostrador saturado el día de inscripción |
| Concejalía de cultura / educación | Alta política | Media | Oferta visible, no portal electoral |
| Web institucional del Ayuntamiento | Competencia / canal paralelo | — | No copiar mega-menú de sede electrónica |
Mapa de poder (resumen)
- Decisor de adopción: coordinación de programa / dirección de centro.
- Usuario frecuente de captura: vecina (search + form) y COORD (inbox diario).
- Riesgo de rechazo: si la home no deja buscar en el primer viewport, o si el form implica “ya estás inscrita”.
4. Personas
P1 — Rosa Belmonte · Vecina / participante
| Campo | Detalle |
|---|---|
| Edad / contexto | 54 años; Usera; busca cerámica o escritura cerca, preferible tarde |
| Digital | Media; móvil grande; usa Google; evita “sedes electrónicas” con certificado |
| Goals | Ver si hay taller cerca, si es accesible, si queda hueco; dejar datos y un código |
| Pains | PDFs, teléfonos que no cogen, no saber si “apuntarse” es matrícula |
| Quote de diseño | “Dime si es los martes en Usera y si de verdad me van a llamar.” |
| Seed demo | rosa.belmonte@example.com · solicitud cerámica PZ-0813-001 |
Escenario: Abre PIZARRA, escribe “cerámica”, chip Usera, entra a Barro de Usera, solicita plaza, guarda PZ-0813-001.
[DECISIÓN DE DISEÑO] Rosa no tiene cuenta. Crear usuario municipal sería fricción y no aporta al job de captura.
P2 — Inés Marzal · COORD
| Campo | Detalle |
|---|---|
| Edad / contexto | 38 años; coordinación de talleres en distrito; parte del día en centro, parte en mesa |
| Digital | Media–alta; desktop para la cola; móvil para devolver llamadas |
| Goals | Ver NEW del día, contactar en 48 h, inscribir o pasar a WAITLIST sin Excel |
| Pains | Correo mezclado con “¿queda plaza?”; no sabe quién ya fue llamado; overbooking informal |
| Quote de diseño | “Si está en NEW, es mía. Si está ENROLLED, hay silla. Si no, WAITLIST.” |
| Email demo | coord@pizarra.ayto / password123 |
Escenario: Login → inbox → Rosa NEW → llama → CONTACTED → si hay silla ENROLLED; si el grupo está lleno WAITLIST.
Anti-personas
| Quién | Por qué no es target v1 |
|---|---|
| Alumno de campus online que entrega ejercicios | Eso es LMS, no catálogo de plaza |
| Gestor de contratas / facturación de monitores | Backoffice de proveedores, otro producto |
| Turista de “experiencias” tipo ClassPass | Marketplace, no servicio municipal |
5. JTBD y user stories
Job principal (vecina)
Cuando quiero un taller municipal cerca y que encaje (día, precio, accesibilidad),
quiero buscarlo y dejar mis datos en un solo paso,
para que coordinación me contacte sin perder el mensaje y sin creer que ya tengo plaza.
Job principal (COORD)
Cuando llegan solicitudes de plaza,
quiero verlas con taller, distrito y estado,
para contactar en 48 h e inscribir o poner en espera sin overbooking.
Jobs secundarios
| Job | Rol |
|---|---|
| Filtrar por accesibilidad (rampa, bucle, planta baja) | Vecina |
| Referir una solicitud por código corto en ventanilla | Ambos |
| Ver cuántas NEW / WAITLIST hay hoy | COORD |
| Cancelar duplicado o desistimiento | COORD |
Stories Must (v1)
| ID | Story | AC |
|---|---|---|
| US1 | Como vecina, quiero buscar por texto y chips | GET workshops con q + facetas; resultados o empty |
| US2 | Como vecina, quiero abrir la ficha con aforo, horario y sede | GET :slug; foto real; CTA |
| US3 | Como vecina, quiero enviar solicitud sin cuenta | POST 201 + redirect success |
| US4 | Como vecina, quiero un código y saber que no estoy inscrita | code en /ok + copy disclaimer |
| US5 | Como COORD, quiero entrar con email/password | JWT + redirect /inbox |
| US6 | Como COORD, quiero listar solicitudes y stats | GET list + summary |
| US7 | Como COORD, quiero cambiar estado incl. WAITLIST | PATCH status |
| US8 | Como cualquiera, quiero ver empty / error / loading | UI-08, UI-09, UI-10 |
MoSCoW (v1 L2)
| Prioridad | Ítems |
|---|---|
| Must | Search home, ficha, form, success, login, inbox, detail, 5 estados, empty/loading/error, mobile search |
| Should | Chips activos visibles; aforo vs nº solicitudes en detalle COORD; labels ES de enums |
| Could | Persistencia de query en URL; click-to-call; copiar código al portapapeles |
| Won’t | Pagos, cuenta vecina, CMS talleres, email transaccional, calendario iCal |
6. Journey (vecina → COORD)
| Fase | Actor | Acción | Touchpoint | Emoción [HIPÓTESIS] |
|---|---|---|---|---|
| 1 Preguntar | Rosa | Escribe “cerámica” o toca chip | Home search | Esperanza cautelosa |
| 2 Acotar | Rosa | Chip Usera / gratuito / planta baja | Facetas | Control |
| 3 Entender | Rosa | Lee ficha: martes, aforo 12, sede | /talleres/barro-usera | Confianza o duda de cupo |
| 4 Pedir | Rosa | Form + disclaimer | /solicitar | Prudencia |
| 5 Acuse | Rosa | Ve PZ-0813-001 | /ok | Alivio (no euforia de “inscrita”) |
| 6 Contacto | Inés | Ve NEW, llama | Inbox + detalle | Control operativo |
| 7 Cierre | Inés | ENROLLED o WAITLIST | PATCH | Cierre honesto |
Momentos de verdad
- Search en el primer viewport — si hay que “descubrir el catálogo”, se rompe S2 [HIPÓTESIS].
- Disclaimer en form y success — si falta, H2 falla.
- Código PZ- — prueba de “ha llegado” para ventanilla y llamada.
- WAITLIST visible — evita la mentira social del “ya te apunto”.
7. Service blueprint (resumen)
| Capa | Elementos |
|---|---|
| Frontstage vecina | Search, resultados, ficha, form, success |
| Frontstage COORD | Login, inbox, detalle, botones de estado |
| Backstage | Llamada / SMS / mostrador (fuera de app) |
| Sistemas | Nest API, Prisma, Neon, JWT, Angular search |
| Soportes | Seed 6 talleres + fotos, Paper, docs |
| Fallos | 0 resultados; 401 sin token; 404 slug; red caída → UI error |
Fallos de servicio y respuesta de diseño
| Fallo | Evidencia de UI | Recuperación |
|---|---|---|
| Query sin hits | UI-08 Empty search | “Prueba sin distrito” + chips sugeridos |
| API caída en search | UI-09 Error | Reintentar |
| Latencia catálogo | UI-10 Loading | Skeleton de cards |
| Form inválido | Inline field | No navegar a success |
| Cupo lleno (ops) | COORD ve seats vs count | Forzar WAITLIST, no ENROLLED [H3] |
8. Matriz hallazgo → requisito → feature
| Hallazgo | Tipo | Requisito | Feature v1 |
|---|---|---|---|
| Llega con pregunta, no con marca | [SUPUESTO] S2 | Search es la home | UI-01 / H-SEARCH |
| PDF / listado no filtra | [HIPÓTESIS] H1 | Facetas disciplina/distrito/precio/a11y | Query params + chips |
| Sin acuse | [HIPÓTESIS] | Código oral | Success + PZ- |
| Confunden solicitud con plaza | [HIPÓTESIS] H2 | Copy irrenunciable | Disclaimer form/success |
| Overbooking informal | [HIPÓTESIS] H3 | Estado WAITLIST + seats | Enum + campo Workshop.seats |
| PII de vecindad | [DECISIÓN] | Auth JWT COORD | Login + guards API |
| L2 compacto, no LMS | [DECISIÓN] | Un rol COORD; Workshop seed | Sin CMS, sin cuenta vecina |
9. Preguntas abiertas (no bloquean v1)
| ID | Pregunta | Cómo se resolvería después |
|---|---|---|
| Q1 | ¿Un COORD por distrito o cola única de programa? | districtScope en User L2+ |
| Q2 | ¿Email automático “hemos recibido tu solicitud”? | Hook post-create; copy ya promete llamada |
| Q3 | ¿Checkbox RGPD + política municipal? | Legal L2+; minimización ya aplicada |
| Q4 | ¿Sincronizar aforo con mostrador presencial? | Fuente de verdad única; hoy es COORD |
| Q5 | ¿Prioridad de empadronamiento en el distrito? | Campo residentDistrict + regla ops; no auto-ranking v1 |
10. Plan de research futuro (si hubiera campo real)
| Método | Muestra orientativa | Pregunta |
|---|---|---|
| Test de usabilidad search | 5–6 vecinas 45–70 | ¿Encuentran taller en < 60 s? |
| Shadowing COORD 1 jornada | 1–2 centros | ¿El inbox sustituye el Excel del día? |
| Revisión de reclamaciones | Tickets “ya inscrito” | Validar H2 |
| Card sort de disciplinas | 6–8 participantes | ¿Nombres de faceta comprensibles? |
Estos métodos no se han ejecutado. No se reportan hallazgos como si lo hubieran sido.
11. Síntesis
PIZARRA se diseña como catálogo cívico con cola de contacto, no como campus ni como sede electrónica.
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 — PIZARRA
1. Principios de IA
| Principio | Aplicación |
|---|---|
| Search first | La home es el buscador + resultados. No hay landing de marca por delante. |
| Pregunta → ficha → solicitud | Profundidad 2 desde query hasta form |
| Público vs COORD | Catálogo y form abiertos; inbox solo autenticado |
| Lenguaje de dominio | Taller, plaza, solicitud, distrito, sede — no “curso SKU”, “lead CRM”, “ticket” |
| Código visible | PZ-… en success e inbox para referencia oral / ventanilla |
| Honestidad de estado | Solicitud ≠ inscrita; WAITLIST es ciudadano de primera |
2. Sitemap
/ Home search (público) ?q & disciplina & distrito & precio & acceso
/talleres/:slug Ficha de taller (público)
/talleres/:slug/solicitar Formulario de solicitud (público)
/ok Success post-solicitud (público)
/login Login JWT COORD
/inbox Lista + stats (auth COORD)
/inbox/:id Detalle + estado (auth COORD)
/** → redirect /
Árbol por audiencia
| Audiencia | Nodos relevantes |
|---|---|
| Vecina | Home search → Ficha → Solicitar → Ok |
| COORD | Login → Inbox → Detail (y catálogo público si consulta oferta) |
| Ambos | Wordmark → / (COORD autenticada: wordmark puede ir a /inbox) |
3. Navegación
Pública (S-CMD)
| Elemento | Destino | Notas |
|---|---|---|
| Wordmark PIZARRA | / | Newsreader; no “portal del Ayuntamiento” |
| Campo de búsqueda | / (focus / submit) | Superficie principal; placeholder de query |
| Chips de faceta | / + query params | Disciplina, distrito, precio, accesibilidad |
| Card de taller | /talleres/:slug | Resultado |
| Solicitar plaza | /talleres/:slug/solicitar | Primary en ficha |
| Coordinación | /login | Entrada discreta (no compite con search) |
[DECISIÓN DE DISEÑO] No hay mega-menú de concejalías ni “Áreas de gobierno”. Eso copiaría sede electrónica y rompería R-CL excelente (aula, no portal).
COORD (autenticado)
| Elemento | Destino | Visibilidad |
|---|---|---|
| Wordmark / Inbox | /inbox | COORD |
| Badge rol | — | COORD |
| Nombre | Inés Marzal | COORD |
| Catálogo | / | Opcional, para consultar oferta |
| Salir | limpia token → /login | COORD |
| Card solicitud | /inbox/:id | COORD |
| Volver (detalle) | /inbox | COORD |
No hay sidebar multi-sección en v1 (evita IA de CRM / S-SIDE de PRIMA).
4. Inventario de contenido
| Pantalla | Contenidos |
|---|---|
| Home search | Wordmark, campo query, chips, recuento, grid de cards (foto, título, distrito, horario, precio, a11y), empty, loading |
| Ficha | Hero foto, título, disciplina, distrito, sede, horario, aforo, precio, accesibilidad, descripción, disclaimer, CTA |
| Solicitar | Título taller, disclaimer, campos form, submit |
| Success | Mensaje, código PZ-, “no es plaza”, siguiente paso |
| Login | Título panel coordinación, email, password, submit, error |
| Inbox | Stats (total, open, byStatus), lista, empty, error |
| Detail | Código, vecina, taller, aforo vs demanda, notas, acciones estado |
5. Taxonomía
RequestStatus
| Status API | Label UI | Semántica |
|---|---|---|
NEW | Nueva | Acaba de llegar; sin contacto |
CONTACTED | Contactada | COORD inició llamada / mensaje |
ENROLLED | Inscrita | Plaza administrativa cerrada |
WAITLIST | Lista de espera | Cupo cubierto o reserva de hueco |
CANCELLED | Cancelada | Desiste / duplicado / no localizable |
Orden de inbox: createdAt descendente (más reciente primero).
[DECISIÓN DE DISEÑO] No se ordena por “prioridad de empadronamiento” en v1 (Q5 abierta).
Discipline
| API | Label UI | Talleres seed |
|---|---|---|
CERAMICS | Cerámica | Barro de Usera |
WRITING | Escritura | Palabra en Vallecas |
THEATRE | Teatro | Escena Latina |
COOKING | Cocina | Fogón Carabanchel |
ROBOTICS | Robótica | Bits Villaverde |
MUSIC | Música | Coro Arganzuela |
District
| API | Label UI |
|---|---|
USERA | Usera |
VALLECAS | Vallecas |
LATINA | Latina |
CARABANCHEL | Carabanchel |
VILLAVERDE | Villaverde |
ARGANZUELA | Arganzuela |
PriceBand (faceta)
| API | Label UI | Criterio seed |
|---|---|---|
FREE | Gratuito | feeCents = 0 |
FEE | Con tasa | feeCents > 0 (importe en ficha) |
AccessibilityTag (faceta, multi)
| API | Label UI | Semántica seed |
|---|---|---|
STEP_FREE | Sin escalones | Planta baja / rampa |
LOOP | Bucle magnético | Sala con bucle |
SEATED | Opción sentada | Alternativa a suelo / movimiento |
LARGE_PRINT | Letra grande | Material en cuerpo mayor |
Las etiquetas de accesibilidad del seed son atributos de producto, no una auditoría real de cada centro [SUPUESTO de catálogo demo].
6. Modelo mental vs UI
| Modelo mental | Representación |
|---|---|
| “¿Hay cerámica cerca?” | Query + chip distrito |
| “¿Es los martes y subo en silla?” | Ficha: horario + tags a11y |
| “Me apunto” | Form /solicitar + disclaimer |
| “Me dieron un número” | code PZ- |
| “Lista del día de gente nueva” | Inbox + badge NEW |
| “Ya le llamé” | CONTACTED |
| “Tiene silla” | ENROLLED |
| “El grupo está lleno” | WAITLIST |
7. Query string (contrato de search)
| Param | Ejemplo | Efecto |
|---|---|---|
q | ceramica | Búsqueda texto (título, sede, descripción) |
discipline | CERAMICS | Faceta disciplina |
district | USERA | Faceta distrito |
price | FREE | Faceta precio |
access | STEP_FREE | Faceta accesibilidad (contains) |
[DECISIÓN DE DISEÑO] Facetas en URL para compartir / volver atrás. Combinación AND. Texto q es AND con facetas.
8. Rutas API alineadas a IA
| UI | API |
|---|---|
| Home resultados | GET /api/workshops?q&discipline&district&price&access |
| Ficha | GET /api/workshops/:slug |
| Submit solicitud | POST /api/requests |
| Login | POST /api/auth/login |
| Inbox list | GET /api/requests |
| Stats | GET /api/requests/stats/summary |
| Detail | GET /api/requests/:id |
| Cambiar estado | PATCH /api/requests/:id/status |
9. Decisiones de IA descartadas
| Idea | Por qué no en L2 v1 |
|---|---|
/explorar editorial + search secundario | Rompe H-SEARCH |
| Área “mis solicitudes” por email mágico | Cuenta de facto; authz delicada |
| Calendario mensual de todos los centros | Ruido; S2 es query, no month-view |
| Nested nav “Cultura → Distritos → Centros → …” | Sede electrónica |
| Kanban de solicitudes | F-KAN es de PRIMA; aquí la cola es inbox |
| Wizard de 4 pasos de inscripción | F-ONB de CANTERA; el form es 1 pantalla |
04-user-flows.md
04 — Flujos de usuario — PIZARRA
Convenciones
- Actor: Guest (vecina) | COORD
- Éxito: resultado observable
- Errores: UI + código HTTP cuando aplica
- Flujo canónico: F-SEARCH
F1 — Descubrimiento por búsqueda (Guest)
/ → focus en campo search (H-SEARCH)
→ escribe query y/o activa chips
→ GET /api/workshops?q=&discipline=&district=&price=&access=
→ grid de cards | empty | error | skeleton
→ click card → /talleres/:slug
→ (nav discreta) "Coordinación" → /login
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Aterriza; el search es el primer foco visual | Render home |
| 2 | Query y/o chips; URL se actualiza | Router query params |
| 3 | Ve resultados con foto, distrito, horario | GET workshops |
| 4 | Abre ficha o refina facetas | Navegación / re-fetch |
Éxito: al menos una ficha alcanzable, o empty accionable.
AC: no hay que hacer scroll de “marca” para llegar al input.
Errores / estados
| Caso | Comportamiento |
|---|---|
| 0 resultados | UI-08: “Ningún taller con esos filtros” + quitar última faceta |
| Red / 5xx | UI-09 + Reintentar |
| Primera carga | UI-10 skeleton de 6 cards |
F2 — Entender taller (Guest)
/talleres/:slug → GET /api/workshops/:slug
→ foto, aforo, horario, sede, precio, a11y, disclaimer
→ CTA "Solicitar plaza" → /talleres/:slug/solicitar
→ 404 slug → mensaje + volver a search
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee oferta presencial | Workshop |
| 2 | Contrasta aforo y tags a11y | Campos seats, accessTags |
| 3 | Decide pedir o volver a resultados | Router |
Éxito: CTA visible; disclaimer “solicitar no reserva silla” visible antes del form.
Error: 404 si slug inexistente.
F3 — Solicitar plaza (Guest, público)
/talleres/:slug/solicitar
→ validación cliente
→ POST /api/requests {
workshopId,
fullName, email, phone?,
residentDistrict?,
notes?,
accessNeed?
}
→ 201 SeatRequest { code, status: NEW, ... }
→ /ok?code=PZ-…
| Campo | Validación cliente (mín.) | API |
|---|---|---|
| workshopId | required (de la ruta) | existe en DB |
| fullName | required, min 2 | @MinLength(2) |
| required, email | @IsEmail() | |
| phone | optional | @IsOptional() |
| residentDistrict | optional enum District | @IsOptional() @IsEnum |
| notes | optional | default "" |
| accessNeed | optional texto libre | @IsOptional() |
Éxito: registro status=NEW, código PZ-MMDD-NNN, pantalla success.
No hay cuenta de vecina en v1.
Errores
| Caso | Comportamiento |
|---|---|
| Validación DTO | 400 + mensaje de campo |
| workshopId 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 |
[DECISIÓN DE DISEÑO] No se bloquea el POST aunque seats estén cubiertos: el vecino puede entrar en WAITLIST después, por decisión COORD. Bloquear en cliente mentiría sobre el mostrador.
F4 — Success (Guest)
/ok → copy de acuse + code
→ “Esto no confirma la plaza”
→ CTA volver a buscar / ir al taller
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee acuse y código | UI (query/state) |
| 2 | Conserva el código (foto, papel, nota) | Fuera de app |
| 3 | Espera llamada / SMS de coordinación | Fuera de app |
AC: el código es seleccionable; el disclaimer es visible sin scroll en desktop.
F5 — Login JWT (COORD)
/login → POST /api/auth/login { email, password }
→ 200 { accessToken, user } → localStorage → /inbox
→ 401 → mensaje error en form
| Campo | Validación cliente | API |
|---|---|---|
| required, email | @IsEmail() | |
| password | required, min 6 | @MinLength(6) |
Éxito: token guardado; user role=COORD, nombre Inés Marzal.
Credencial demo: coord@pizarra.ayto / 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 + stats (COORD)
/inbox (token en cliente)
→ GET /api/requests
→ GET /api/requests/stats/summary
→ Render cards + métricas
| Caso | Comportamiento |
|---|---|
| Lista con items | Cards: code, nombre, taller, distrito, status, createdAt |
| Lista vacía | Empty: “No hay solicitudes todavía.” |
| Fallo red / 401 | Error + reintentar / re-login |
| Stats | total, open (NEW+CONTACTED), waitlist, byStatus |
[DECISIÓN DE DISEÑO] Stats son de lectura en v1; el click no filtra (filtro inbox = L2+).
F7 — Detalle y cambio de estado (COORD)
/inbox/:id
→ GET /api/requests/:id
→ UI: contacto + taller + seats + nº solicitudes del taller + notas
→ PATCH /api/requests/:id/status { status }
→ 200 SeatRequest actualizado
Transiciones típicas (máquina simple L2)
| Desde | Hacia | Intención |
|---|---|---|
| NEW | CONTACTED | Inés inició contacto |
| CONTACTED | ENROLLED | Hay silla; inscripción administrativa |
| CONTACTED | WAITLIST | Cupo cubierto / pendiente de baja |
| NEW | WAITLIST | Grupo ya lleno al abrir la solicitud |
| * | CANCELLED | Desiste, duplicado, no localizable |
| WAITLIST | ENROLLED | Se libera silla |
| ENROLLED | CANCELLED | Baja posterior |
| * | * | Corrección operativa (API acepta enum) |
Éxito: badge actualizado; lista refleja al volver.
Errores: 404 id; 401 sin token; 400 status inválido.
Regla de integridad H3 (ops, no trigger DB v1): COORD no debe marcar ENROLLED si enrolledCount >= seats. La UI avisa; el API L2 v1 no rechaza (corrección humana). Rechazo duro = L2+.
F8 — Logout (COORD)
Click "Salir" → 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 COORD
- “Mis solicitudes” por código público
- Notificación email automática
- Filtros multi-criterio en inbox
- Asignación multi-COORD por distrito
- Pago de tasa
- Edición de catálogo Workshop por UI
05-data-model.md
05 — Modelo de datos — PIZARRA
1. Visión general
Dominio L2 de catálogo cívico + cola de plaza:
| Entidad | Propósito |
|---|---|
| User | Identidad de coordinación (rol COORD) |
| Workshop | Taller publicado (oferta) |
| SeatRequest | Solicitud de plaza ligada a un taller |
Base: PostgreSQL (Neon) · ORM: Prisma · IDs: cuid().
2. Enums
Role
| Valor | Descripción |
|---|---|
COORD | Operadora del inbox; único rol autenticado v1 |
RequestStatus
| Valor | Descripción |
|---|---|
NEW | Recién creada por el form público |
CONTACTED | COORD ha iniciado contacto |
ENROLLED | Plaza administrativa confirmada |
WAITLIST | En espera de silla |
CANCELLED | Anulada |
Discipline
CERAMICS | WRITING | THEATRE | COOKING | ROBOTICS | MUSIC
District
USERA | VALLECAS | LATINA | CARABANCHEL | VILLAVERDE | ARGANZUELA
PriceBand
FREE | FEE — derivado de feeCents en queries; no hace falta persistirlo si se calcula.
AccessibilityTag
STEP_FREE | LOOP | SEATED | LARGE_PRINT
3. Diagrama ER (texto)
User
id, email, passwordHash, name, role(COORD)
createdAt, updatedAt
Workshop
id, slug (unique), title, summary, description
discipline, district, venue, address
scheduleLabel, startsOn, endsOn
seats, feeCents
accessTags String[] // AccessibilityTag
imageKey // p.ej. taller-ceramica.jpg
createdAt, updatedAt
1 ──< SeatRequest
SeatRequest
id, code (unique)
workshopId → Workshop
fullName, email, phone?
residentDistrict?
notes, accessNeed?
status (default NEW)
createdAt, updatedAt
No hay FK entre SeatRequest y User en v1: el form es anónimo. COORD opera sobre el conjunto global.
4. Tablas / modelos Prisma
User
| Campo | Tipo | Constraints |
|---|---|---|
| id | String | PK, cuid |
| String | unique | |
| passwordHash | String | bcrypt |
| name | String | |
| role | Role | default COORD |
| createdAt | DateTime | default now |
| updatedAt | DateTime | updatedAt |
Workshop
| Campo | Tipo | Constraints |
|---|---|---|
| id | String | PK, cuid |
| slug | String | unique, kebab-case |
| title | String | |
| summary | String | card (~140) |
| description | String | ficha |
| discipline | Discipline | enum |
| district | District | enum |
| venue | String | nombre del centro |
| address | String | dirección corta |
| scheduleLabel | String | “Martes 18:00–20:00” |
| startsOn | DateTime | inicio de edición |
| endsOn | DateTime | fin de edición |
| seats | Int | aforo > 0 |
| feeCents | Int | 0 = gratuito |
| accessTags | String[] | tags a11y |
| imageKey | String | filename en assets |
| createdAt / updatedAt | DateTime |
SeatRequest
| Campo | Tipo | Constraints |
|---|---|---|
| id | String | PK, cuid |
| code | String | unique, formato PZ-MMDD-NNN |
| workshopId | String | FK Workshop |
| fullName | String | |
| String | ||
| phone | String? | opcional |
| residentDistrict | District? | opcional |
| notes | String | default "" |
| accessNeed | String? | texto libre (no sustituye tags del taller) |
| status | RequestStatus | default NEW |
| createdAt / updatedAt | DateTime |
5. Reglas de integridad y negocio
| Regla | Implementación |
|---|---|
| Create público | POST /api/requests sin guard |
| List/detail/status | JwtAuthGuard → 401 sin token |
| Catálogo público | GET /api/workshops y :slug sin guard |
| Código único | code unique; PZ-MMDD- + secuencia 001–999 del día [DECISIÓN] (fallback random si colisión) |
| Status default | NEW al crear |
| Password | Nunca en claro; solo passwordHash |
| Notas | Coalesce a "" si omitidas |
| Orden listado solicitudes | createdAt desc |
| Orden catálogo | district asc, title asc (estable; no “destacados”) |
| Search texto | contains case-insensitive en title, summary, venue |
| Facetas | AND entre params; access = has tag |
| ENROLLED vs seats | Aviso UI; no check DB v1 (H3 operativa) |
6. Contratos API (resumen)
GET /api/workshops (público)
Query: q?, discipline?, district?, price? (FREE|FEE), access?
Response 200: Workshop[] (campos de card + ficha; sin PII).
GET /api/workshops/:slug (público)
Workshop o 404.
POST /api/requests (público)
Body
| Campo | Tipo | Req |
|---|---|---|
| workshopId | string | sí |
| fullName | string | sí |
| string | sí | |
| phone | string | no |
| residentDistrict | District | no |
| notes | string | no |
| accessNeed | string | no |
Response 201: SeatRequest + workshop embebido mínimo (title, slug, district).
POST /api/auth/login
Body: { email, password }
Response 200: { accessToken, user: { id, email, name, role } }
GET /api/requests (JWT)
Array SeatRequest + workshop, createdAt desc.
GET /api/requests/stats/summary (JWT)
{
"total": 6,
"open": 3,
"waitlist": 1,
"byStatus": {
"NEW": 2,
"CONTACTED": 1,
"ENROLLED": 1,
"WAITLIST": 1,
"CANCELLED": 1
}
}
open = NEW + CONTACTED.
GET /api/requests/:id (JWT)
SeatRequest + workshop (seats) + enrolledCount del taller (agregado) o 404.
PATCH /api/requests/:id/status (JWT)
Body: { status: RequestStatus }
Response: SeatRequest actualizado.
7. Seed de referencia (2026-08-13)
COORD
| Nombre | Password | |
|---|---|---|
| Inés Marzal | coord@pizarra.ayto | password123 |
Workshops
| slug | Título | Disciplina | Distrito | Sede (demo) | Horario | Seats | Tasa | A11y | Asset |
|---|---|---|---|---|---|---|---|---|---|
| barro-usera | Barro de Usera | Cerámica | Usera | C.C. Usera | Mar 18:00–20:00 | 12 | 0 | STEP_FREE, SEATED | taller-ceramica.jpg |
| palabra-vallecas | Palabra en Vallecas | Escritura | Vallecas | C.C. Puente de Vallecas | Jue 17:30–19:30 | 16 | 0 | LOOP, LARGE_PRINT | taller-escritura.jpg |
| escena-latina | Escena Latina | Teatro | Latina | C.C. Aluche | Mié 19:00–21:00 | 14 | 1500 | STEP_FREE | taller-teatro.jpg |
| fogon-carabanchel | Fogón Carabanchel | Cocina | Carabanchel | C.C. San Francisco | Sáb 11:00–13:00 | 10 | 2000 | SEATED | taller-cocina.jpg |
| bits-villaverde | Bits Villaverde | Robótica | Villaverde | C.C. San Cristóbal | Vie 17:00–19:00 | 12 | 0 | STEP_FREE | taller-robotica.jpg |
| coro-arganzuela | Coro Arganzuela | Música | Arganzuela | C.C. Legazpi | Lun 19:00–20:30 | 20 | 0 | LOOP, LARGE_PRINT | taller-musica.jpg |
Nombres de centros y horarios son contenido de producto demo, no programación oficial publicada [SUPUESTO de catálogo].
SeatRequests
| Código | Nombre | Taller | Estado | Notas demo |
|---|---|---|---|---|
| PZ-0813-001 | Rosa Belmonte | barro-usera | NEW | “Prefiero mesa baja; vengo de Pradolongo.” |
| PZ-0813-002 | Luis Aranda | palabra-vallecas | CONTACTED | — |
| PZ-0813-003 | Marta Gil | coro-arganzuela | ENROLLED | — |
| PZ-0813-004 | Youssef Amrani | fogon-carabanchel | WAITLIST | Grupo lleno en mostrador |
| PZ-0813-005 | Pilar Soto | escena-latina | CANCELLED | Duplicada |
| PZ-0813-006 | Carmen Ruiz | bits-villaverde | NEW | Hija 12 años, acompaña |
El sufijo de fecha del código de seed es el día del caso; la generación runtime usa la fecha del servidor.
8. Evolución posible (no implementada)
| Cambio | Nivel |
|---|---|
coordDistrict en User + filtro de cola | L2+ |
| Check DB ENROLLED < seats | L2+ |
| Tabla Venue / Center normalizada | L3 |
| Soft delete + audit log de status | L2+ |
| CMS Workshop autenticado | L3 |
| User VECINA con historial | L3 |
06-tech-stack.md
06 — Stack tecnológico — PIZARRA
1. Visión
| Capa | Tecnología | Notas |
|---|---|---|
| Frontend | Angular standalone + signals | Puerto 4200 |
| Estilos | Tailwind CSS | Tokens chalky (bg/ink/chalk/moss) |
| Backend | NestJS TypeScript | Puerto 3013, prefijo /api |
| ORM | Prisma | Workshop, SeatRequest, User |
| DB | Neon PostgreSQL | project crimson-voice-36926751 |
| Auth | JWT passport | Rol COORD |
| Diseño | Paper | 01KZWYWE6BFRD96MAAF8HQWJ78 |
| Repo | /Users/cristian/orca/pizarra-app/ | GitHub Criscode2022/pizarra-app |
Stack fijo del cron. No React/Next/Firebase.
2. Estructura
pizarra-app/
├── apps/api/ # Nest + Prisma (independiente)
└── apps/web/ # Angular + Tailwind (independiente)
D-P1-03: npm --prefix, sin workspaces npm.
3. API pública vs JWT
| Método | Ruta | Auth |
|---|---|---|
| GET | /api/workshops | Público + query/facetas |
| GET | /api/workshops/:slug | Público |
| POST | /api/requests | Público |
| POST | /api/auth/login | Público |
| GET | /api/requests | JWT COORD |
| GET | /api/requests/stats/summary | JWT |
| GET | /api/requests/:id | JWT |
| PATCH | /api/requests/:id/status | JWT |
4. Justificación
Angular + Nest alinean con el resto de la serie y con el handoff del estudio. Neon serverless encaja con un catálogo municipal de volumen bajo. Search-first no requiere Elasticsearch: ILIKE + índices de disciplina/distrito bastan para 8–20 talleres.
5. Variables
.env local (nunca git): DATABASE_URL, JWT_SECRET, PORT=3013.
.env.example solo placeholders.
07-creative-direction.md
07 — Dirección creativa — PIZARRA
Mood
Chalky — pizarra de aula municipal al final de clase. No bookish (biblioteca) ni mineral (obra).
Candidatos descartados: chapel, botanical, mineral, bookish.
Paleta
| Rol | Hex | Referente |
|---|---|---|
| Ground | #E4EAF2 | Polvo de tiza |
| Surface | #EEF2F8 | Papel de lista |
| Ink | #1A231E | Pizarra verde-negra |
| Muted | #4A564F | Fieltro del rail |
| Accent | #D4A017 | Tiza amarilla |
| Secundario | #3F6B5A | Borrador musgo |
| Rail | #D7D0C2 | Listón de madera |
| Danger | #9B3A2F | Óxido de tiza roja |
Tipo
- Display: Newsreader 600 — institucional, editorial de centro cívico.
- UI: Atkinson Hyperlegible 400/700 — legibilidad cívica, no moda.
Evita Outfit, Archivo, Fraunces, Syne (cooldown).
Principios
- La búsqueda es el monumento, no un hero 2 columnas.
- Radio 6–10px. Sin píldoras atléticas.
- Foto real del taller, nunca color sólido.
- Copy honesto: solicitud ≠ plaza.
- Contraste AA sobre polvo de tiza; labels ≥13px.
08-design-system.md
08 — Design system — PIZARRA
Tokens
Ver Paper UI-11 y apps/web/tailwind.config.js.
| Token | Valor |
|---|---|
| bg | #E4EAF2 |
| surface | #EEF2F8 |
| ink | #1A231E |
| ink-muted | #4A564F |
| chalk | #D4A017 |
| moss | #3F6B5A |
| rail | #D7D0C2 |
| danger | #9B3A2F |
| display | Newsreader |
| sans | Atkinson Hyperlegible |
| radius | 6 / 10 / 16 |
Componentes
| Pieza | Uso |
|---|---|
| Command bar | Home: input 20px + CTA tiza |
| Chip / facet | On = ink+blanco; off = surface+rail |
| Search hit | Foto 168×112 + meta + título + plazas |
| Button primary | Fondo chalk, texto ink |
| Button secondary | Fondo ink, texto blanco |
| Button ghost | Borde ink |
| Badge NEW | chalk |
| Badge WAITLIST | ink |
| Badge ENROLLED | moss |
| Form field | Borde ink 1px, label 13px bold |
| Disclaimer | Fondo #E8D48A |
| Skeleton | rail blocks, 3 filas |
Estados
Empty, loading, error, validación email, 401 inbox, aforo 0 → WAITLIST.
No usar
Rounded-full lime, hero 2-col, badges genéricos de “SaaS”, emoji como icono.
09-content-guide.md
09 — Guía de contenido — PIZARRA
Voz
Municipal, cercana, concreta. Segunda persona de usted evitada: tuteo vecinal. Sin jerga de LMS.
Palabras permitidas / prohibidas
| Usar | No usar |
|---|---|
| Solicitud de plaza | Reserva confirmada, inscripción automática |
| Coordinación te contacta | «Ya estás dentro» |
| Lista de espera | Overbooking |
| Taller | Curso online, masterclass |
| Distrito / sede | Campus |
| Gratis / 15 € | Freemium |
Microcopy clave
- Home: «¿Qué taller buscas?»
- Disclaimer: «Pedir plaza no confirma inscripción.»
- Success: «Guarda este código. No estás inscrita todavía.»
- Empty: «Nada con esos filtros.»
- Error: «Tu búsqueda se conserva. Reintenta.»
- Inbox: «Cola del trimestre · aforo real»
Códigos
Formato PZ-MMDD-NNN. Se lee en voz alta. No UUID.
Idioma
es-ES. Futuro: fácil lectura / EN no en v1.
10-accessibility.md
10 — Accesibilidad — PIZARRA
Objetivo: WCAG 2.2 AA. No se declara conformidad legal certificada.
Decisiones
| Tema | Decisión |
|---|---|
| Tipo UI | Atkinson Hyperlegible |
| Cuerpo | ≥16px |
| Contraste | Ink #1A231E sobre #E4EAF2; chalk+ink para CTA |
| Foco | Outline 2px ink visible |
| Facetas | fieldset + legend, no solo chips mudos |
| Imágenes | alt descriptivo del taller |
| Form | Labels visibles, no placeholder-only |
| Errores | Texto, no solo color |
| Mobile | Targets ≥44px en chips y CTA |
| Movimiento | Sin parallax; skeleton estático |
Facetas de a11y de producto
Filtro «rampa / ascensor / bucle / planta baja» describe la sede, no sustituye auditoría física.
Riesgos
- Demasiados chips en móvil: wrap + fieldset.
- Chalk
#D4A017solo como fondo de botón con texto ink (no texto chalk 13px sobre dust). - Status bar iOS en Paper no es producto.
QA a11y (mínimo)
- Navegar home → ficha → form solo teclado.
- Lighthouse a11y ≥ 90 en search.
- Contraste de chips on/off.
- Lector: legend de disciplina anunciado.
11-privacy-security.md
11 — Privacidad y seguridad — PIZARRA
No es un dictamen legal.
Datos
| Dato | Clasificación | Uso |
|---|---|---|
| Nombre, email, teléfono | PII | Contactar solicitud |
| Notas | PII opcional | Contexto COORD |
| Taller, estado, código | Operativo | Cola y aforo |
| Password hash | Secreto | Solo COORD |
Sin datos de salud, menores con tutor, ni pagos.
Base (supuesto de diseño)
Interés público / misión de servicio cultural + consentimiento de contacto al enviar. Texto del form debe mencionarlo.
Retención
Solicitudes: 24 meses (hipótesis operativa). Luego borrado o anonimización. Demo Neon no es producción.
Seguridad v1
- TLS hacia Neon.
- JWT COORD, expiración por defecto Nest/passport.
- Validación DTOs.
- Público no lista PII.
- ENROLLED incrementa aforo una vez (evita overbooking).
- Secretos fuera de git.
Riesgos
Demo con emails ficticios. Si se publica, rotar JWT_SECRET y no reutilizar Neon de demo.
12-analytics.md
12 — Analítica — PIZARRA
North star
% de solicitudes con taller + email válidos que pasan a CONTACTED ≤ 48 h.
Eventos
| Evento | Dónde | Props |
|---|---|---|
search_submitted | Home | q, result_count |
facet_toggled | Home | key, value |
workshop_opened | Hit / ficha | slug |
request_started | Form | slug |
request_submitted | POST OK | code, waitlist |
request_failed | POST error | reason |
coord_login | Login | — |
status_changed | Inbox detalle | from, to |
Funnel
Land → search (resultado>0) → view_workshop → submit → contacted ≤48h → enrolled.
No vanity
Pageviews de home no son éxito. Un PDF institucional ya tenía visitas.
Privacidad
Sin fingerprinting. Eventos sin email en claro en analytics v1 (hash o solo code).
13-qa-test-plan.md
13 — Plan de pruebas — PIZARRA
Smoke obligatorio (D-P1-06)
GET /api/workshops→ ≥6 ítems.POST /api/requestscon slug real → códigoPZ-.POST /api/auth/logincoord@pizarra.ayto / password123 →accessToken.GET /api/requestsBearer → incluye el POST.ng buildOK.
Casos
| ID | Caso | Esperado |
|---|---|---|
| S1 | Query «cerámica» | Solo CERAMICA |
| S2 | Faceta Usera | Solo Usera |
| S3 | Taller lleno | POST status WAITLIST |
| S4 | Email inválido | 400, form error |
| S5 | Success | Código visible, copy no inscrito |
| S6 | Inbox sin JWT | 401 / redirect login |
| S7 | ENROLLED 2 veces | seatsTaken +1 una vez |
| S8 | Empty | CTA quitar filtros |
| S9 | API caída | Error + reintento conserva query |
Hipótesis a validar (no en demo)
H1 facetas bajan abandono. H2 copy honesto baja reclamaciones. H3 WAITLIST evita overbooking.
14-dev-handoff.md
14 — Handoff desarrollo — PIZARRA
Arranque
cd pizarra-app
# apps/api/.env con DATABASE_URL + JWT_SECRET + PORT=3013
npm --prefix apps/api install
npm --prefix apps/web install
npm --prefix apps/api run prisma:generate
npm --prefix apps/api run prisma:migrate
npm --prefix apps/api run prisma:seed
npm run api # :3013
npm run web # :4200
Demo: coord@pizarra.ayto / password123.
Paridad Paper
Home = UI-01 (command + chips + hits). No clonar VOLTA hero 2-col.
Contratos
- Login responde
accessToken(no snake_case). - Search query:
q,discipline,district,level,free. - POST request:
workshopSlug,fullName,email,phone?,notes?.
No hacer
Workspaces npm. Commit de .env. Prometer plaza en copy.
15-roadmap.md
15 — Roadmap — PIZARRA
Hecho en L2 (esta ejecución)
Search + facetas, ficha, solicitud, success, JWT COORD, aforo/WAITLIST, estados, Paper ≥12+12, Neon, docs.
Fuera de alcance L2 (no deuda del día)
- Pagos / TPV
- Cl@ve
- App nativa
- Menores con tutor digital
- Multi-distrito multi-tenant
- Email transaccional real
- Export CSV (Could)
Estas son subidas de nivel, no parches del L2.
16-interaction-specs.md
16 — Especificación de interacción — PIZARRA
Home search
- Enter en la barra dispara búsqueda (mismo que CTA).
- Chip toggle: segundo click quita faceta.
- Hits: fila completa clicable a
/taller/:slug. - Loading: 3 skeletons, query visible.
- Empty: mantiene query; CTA limpia facetas.
Ficha
- CTA primario si
seatsLeft > 0: «Solicitar plaza». - Si 0: «Apuntarme a lista de espera».
- Disclaimer siempre visible antes del CTA.
Form
- Submit deshabilitado si nombre <2 o email inválido.
- Error de red: botón reintentar, no limpia campos.
- Success navega
/ok?code=.
Inbox
- Sin token →
/login. - Acciones de estado: confirmación solo en CANCELLED.
- ENROLLED: si aforo lleno, UI avisa y API rechaza o espera.
Motion
150–200ms fade en resultados. Sin scroll-jacking.
17-prototype-map.md
17 — Mapa de prototipo — PIZARRA
Paper no es clicable vía MCP. El prototipo vivo es la app Angular.
| Ruta | Paper | Rol |
|---|---|---|
/ | UI-01, UI-08, UI-09, UI-10, UI-12 | Vecina |
/taller/:slug | UI-02 | Vecina |
/solicitar/:slug | UI-03 | Vecina |
/ok | UI-04 | Vecina |
/login | UI-05 | COORD |
/inbox | UI-06 | COORD |
/inbox/:id | UI-07 | COORD |
Proceso: UX-00…UX-11. Tokens: UI-11.
18-completeness-audit.md
18 — Auditoría de completitud — PIZARRA (2026-08-13)
1. Alcance auditado
Vertical slice L2: catálogo municipal search-first + solicitud pública de plaza + panel COORD JWT, con docs, referencia Paper, contratos Angular+Nest+Prisma+Neon.
2. Checklist CRON / ALS-2
| Requisito | Estado | Evidencia |
|---|---|---|
| Diversidad sector/tipo/nivel | OK | Cívico L2; no kanban; no wizard; no LMS |
| Terna ≥2 códigos vs N−1/N−2 | OK | R-CL · S-CMD · H-SEARCH · F-SEARCH (brief) |
| Day brief + anti-patrones | OK | docs/00-day-brief.md (no sobrescrito) |
| Paper ≥12 UX + ≥10 UI | OK | UX-count 12 · UI-count 12 · file 01KZWYWE6BFRD96MAAF8HQWJ78 |
| Docs 00–20 | OK | suite en docs/ + executive |
| JWT COORD | OK | Role COORD · D-P1-05 |
| API + seed + Neon | OK | crimson-voice-36926751, port 3013 (contratos) |
| Web tokens | OK | Newsreader/Atkinson · paleta chalky documentada |
| Search-first (no AP-12) | OK | UI-01 / docs 03, 07, 08 |
| Hipótesis no fake field | OK | etiquetas en doc 02 |
| Copy solicitud ≠ plaza | OK | docs 01, 09, 16 |
3. Cobertura funcional
| Feature brief | Spec | UI Paper | API | Docs |
|---|---|---|---|---|
| Home search + chips | Sí | UI-01, UI-12 | GET workshops | 03, 04, 16 |
| Ficha taller + foto | Sí | UI-02 | GET :slug | 05, 09 |
| Form solicitud | Sí | UI-03 | POST requests | 04, 05 |
| Success + PZ- | Sí | UI-04 | code | 04, 09 |
| Login COORD | Sí | UI-05 | POST login | 04, 06 |
| Inbox + stats | Sí | UI-06 | GET + summary | 05 |
| Detail + 5 estados | Sí | UI-07 | GET + PATCH | 04 |
| Empty search | Sí | UI-08 | 200 [] | 16 |
| Error | Sí | UI-09 | 5xx/red | 16 |
| Loading | Sí | UI-10 | pending | 16 |
| Design system | Sí | UI-11 | — | 08 |
| Seed 6+6 | Sí | — | seed | 05 |
4. Cobertura Paper
| 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 notes | Sí |
| UI-01…12 | Search → mobile | Sí |
5. Rúbrica de calidad (auto SCORE orientativo)
| Eje | Score 1–5 | Comentario |
|---|---|---|
| Diversidad | 5 | R-CL recuperado; search-first nuevo vs PRIMA/CANTERA |
| Craft visual | 4–5 | Chalky + Newsreader + fotos taller (Paper/app) |
| Densidad UX docs | 5 | Suite L2 completa, etiquetada |
| Completitud código L2 | — | App fuera de este encargo de documentación |
| Authz | 5 | Contrato: guard en list/detail/status |
| Verdad investigación | 5 | Sin stats de campo falsas |
6. Huecos aceptados (no regresiones de cierre documental)
| Hueco | Clasificación |
|---|---|
| Pagos / LMS / Cl@ve | Fuera L2 |
| e2e automatizado | L2+ |
| Route guards formales Angular | L2+ |
| Check DB aforo | L2+ / L3 |
| CMS talleres | L3 |
| Checkbox RGPD | L2+ documentado |
| Paper/app no tocados aquí | Encargo explícito |
7. Veredicto
COMPLETO para entrega documental del caso 2026-08-13.
La suite define producto L2 usable (tablas, AC, riesgos, contratos) sin presentar investigación de campo como hecho.
Cualquier ampliación LMS/pagos/kanban requiere nuevo brief de diversidad, no parche silencioso.
19-backlog-completo.md
19 — Backlog completo — PIZARRA
Inventario de ítems. Los del alcance L2 documental del día están Done.
El resto es opcional / siguiente nivel, no deuda oculta del cierre.
1. Done — L2 case (2026-08-13)
| ID | Ítem | Capa |
|---|---|---|
| D01 | Definición producto PIZARRA | Docs |
| D02 | Day brief diversidad cívica L2 | Docs (preexistente) |
| D03 | Terna R-CL · S-CMD · H-SEARCH · F-SEARCH | Docs |
| D04 | Personas Rosa / Inés | Docs + Paper ref |
| D05 | JTBD + stories Must | Docs |
| D06 | IA search-first + query params | Docs |
| D07 | Flujos F1–F8 + AC | Docs |
| D08 | Modelo Workshop / SeatRequest / User | Docs |
| D09 | Contratos API :3013 | Docs |
| D10 | Auth JWT COORD | Docs |
| D11 | 5 estados incl. WAITLIST | Docs |
| D12 | Código PZ-MMDD-NNN | Docs |
| D13 | Disclaimer solicitud ≠ plaza | Docs |
| D14 | North star CONTACTED ≤ 48 h | Docs |
| D15 | Tokens chalky + Newsreader/Atkinson | Docs |
| D16 | Paper UX-00…11 + UI-01…12 | Docs |
| D17 | Suite docs 00–20 + README + executive | Docs |
| D18 | Seed 6 talleres + 6 solicitudes especificado | Docs |
| D19 | Assets listados | README |
| D20 | QA smoke S1–S18 | Docs |
2. Backlog L2+ (misma complejidad, implementación / polish)
| ID | Ítem | Prioridad | Notas |
|---|---|---|---|
| B01 | canActivate guards Angular | P1 | UX auth |
| B02 | Filtros por status en inbox | P2 | Query API |
| B03 | Aviso / bloqueo suave ENROLLED ≥ seats | P1 | H3 |
| B04 | Captcha / rate limit POST | P1 | anti-spam |
| B05 | Checkbox privacidad | P1 | RGPD |
| B06 | Playwright smoke S1–S18 | P1 | CI |
| B07 | Skeleton UI-10 en código | P2 | |
| B08 | mailto/tel en detail | P2 | ops |
| B09 | Copy code clipboard | P3 | |
| B10 | API URL por environment | P1 | deploy |
| B11 | aria-live recuento search | P2 | a11y |
| B12 | Página 404 amigable | P3 |
3. Backlog L3 (requiere brief nuevo)
| ID | Ítem | Dependencia |
|---|---|---|
| C01 | COORD por distrito | User.coordDistrict |
| C02 | CMS Workshop | Role EDITOR |
| C03 | Email transaccional | provider |
| C04 | Lookup público por código | authz |
| C05 | Check DB aforo | transacción |
| C06 | Audit log | Event |
| C07 | Calendario iCal | schedule |
| C08 | Ranking empadronamiento | regla Q5 |
4. Backlog explícitamente fuera
| ID | Ítem |
|---|---|
| E01 | LMS / asistencia |
| E02 | Pagos tasa |
| E03 | Cl@ve / sede electrónica |
| E04 | Multi-municipio |
| E05 | Kanban solicitudes |
| E06 | Cuenta de vecina completa |
5. Explicitamente no-backlog
| Idea | Razón |
|---|---|
| Hero 2-col + 3 cards | AP-12 / rompe H-SEARCH |
| Sidebar CRM | S-SIDE de PRIMA |
| Wizard inscripción 4 pasos | F-ONB de CANTERA |
| Marketplace de monitores | Otro producto |
6. Orden de ataque recomendado (si hay continuidad de código)
- Implementar contratos docs 05–06 + seed 05
- B01 guards + B06 e2e + B10 env
- B03 aforo + B08 contact
- B04 + B05 si hay tráfico
- Evaluar brief L3 distrito / CMS
7. Trazabilidad
| Origen | Ítems |
|---|---|
| Day brief must-have | D01–D20 |
| Doc 15 L2+ | B01–B12 |
| Doc 15 L3 / fuera | C01–C08, E01–E06 |
20-implementation.md
20 — Implementación — PIZARRA
1. Resumen ejecutivo técnico
| Campo | Valor |
|---|---|
| App path | /Users/cristian/orca/pizarra-app/ |
| API | NestJS · puerto 3013 · prefijo /api |
| Web | Angular standalone · puerto 4200 |
| DB | Neon PostgreSQL · Prisma · crimson-voice-36926751 |
| Auth | JWT Bearer · role COORD |
| Dominio | User, Workshop, SeatRequest |
| Fecha | 2026-08-13 |
| GitHub | https://github.com/Criscode2022/pizarra-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.
2. Cómo arrancar
cd /Users/cristian/orca/pizarra-app
npm install --prefix apps/api
npm install --prefix apps/web
# Configurar apps/api/.env con DATABASE_URL y JWT_SECRET
npm --prefix apps/api run prisma:migrate
npm --prefix apps/api run prisma:seed
npm run api # http://localhost:3013
npm run web # http://localhost:4200
Credenciales
| Rol | Password | |
|---|---|---|
| COORD | coord@pizarra.ayto | password123 |
3. Módulos API a implementar
Auth
POST /api/auth/login- Valida email/password; compara bcrypt; emite JWT con
sub, email, role. JwtStrategy+JwtAuthGuardprotegen lectura/escritura de solicitudes.
Workshops (público)
| Método | Ruta | Auth | Notas |
|---|---|---|---|
| GET | /api/workshops | No | q, discipline, district, price, access |
| GET | /api/workshops/:slug | No | 404 si no existe |
Filtros: texto contains insensitive en title, summary, venue; facetas AND; price=FREE ⇒ feeCents = 0; access ⇒ accessTags has.
Requests
| Método | Ruta | Auth | Notas |
|---|---|---|---|
| POST | /api/requests | No | create; status NEW; code PZ- |
| GET | /api/requests | JWT | list + workshop; createdAt desc |
| GET | /api/requests/stats/summary | JWT | total, open, waitlist, byStatus |
| GET | /api/requests/:id | JWT | + workshop + enrolledCount; 404 |
| PATCH | /api/requests/:id/status | JWT | body { status } |
Prisma
Enums Role, RequestStatus, Discipline, District; modelos alineados a doc 05; migración init; seed 1 COORD + 6 workshops + 6 requests.
Generación de código
PZ- + MMDD (UTC o Europe/Madrid **[DECISIÓN: Europe/Madrid]**) + - + NNN
NNN = siguiente entero del día, padded 3. Si unique falla, reintentar NNN+1 (máx. 5).
4. Frontend a implementar
| Página | Responsabilidad |
|---|---|
| HomeSearchPage | query params, chips, GET workshops, empty/loading/error |
| WorkshopDetailPage | GET slug, foto, disclaimer, CTA |
| RequestSeatPage | form create público + disclaimer |
| SuccessPage | confirmación + código + “no es plaza” |
| LoginPage | form → ApiService.login → inbox |
| InboxPage | stats + list + empty/error + logout |
| RequestDetailPage | get + patch status + aviso aforo |
ApiService centraliza base URL http://localhost:3013/api, token storage (pizarra_token, pizarra_user), métodos tipados (Workshop, SeatRequest, stats, User).
5. Decisiones de implementación
| Decisión | Razón |
|---|---|
| Puerto API 3013 | Evitar colisión con otros daily apps |
| Workshop 1ª clase | L2 catálogo; search real |
| Search en servidor | URL compartible; source of truth |
| Soft auth en páginas | Simple; API es autoridad |
| scheduleLabel string | Menos fricción timezone |
| Sin FK User–SeatRequest | Form anónimo |
| Código PZ-MMDD-NNN | Referencia oral corta |
| Templates standalone | Velocidad daily |
| Stats endpoint propio | Evolución y menos lógica en cliente |
stats/summary antes de :id | Evitar captura de param |
| No bloquear POST por aforo | H2/H3: COORD decide WAITLIST |
| Home no usa hero como layout | AP-12 / H-SEARCH |
6. Variables de entorno
| Variable | Servicio | Descripción |
|---|---|---|
DATABASE_URL | API | Neon crimson-voice-36926751 |
JWT_SECRET | API | Firma tokens |
PORT | API | opcional, 3013 |
Web: URL de API en ApiService (default localhost:3013).
7. Smoke de implementación (mínimo)
- Seed OK (
PIZARRA seed OK). - GET workshops length 6.
- GET
?q=ceramica&district=USERAdevuelve Barro de Usera. - Login COORD 200.
- List length ≥ 1.
- Create request 201 + code
PZ-. - Patch CONTACTED / WAITLIST / ENROLLED 200.
- GET requests sin token 401.
- Web: search dominante, disclaimer, badges, fotos.
8. Estructura de ficheros clave
pizarra-app/
├── apps/api/
│ ├── prisma/schema.prisma
│ ├── prisma/seed.ts
│ └── src/
│ ├── main.ts
│ ├── app.module.ts
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.strategy.ts
│ │ └── jwt-auth.guard.ts
│ ├── workshops/
│ │ ├── workshops.controller.ts
│ │ └── workshops.service.ts
│ ├── requests/
│ │ ├── requests.controller.ts
│ │ └── requests.service.ts
│ └── prisma/
├── apps/web/
│ ├── tailwind.config.js
│ └── src/app/
│ ├── app.routes.ts
│ ├── core/api.service.ts
│ └── pages/
│ ├── home-search/
│ ├── workshop-detail/
│ ├── request-seat/
│ ├── success/
│ ├── login/
│ ├── inbox/
│ └── request-detail/
└── package.json
9. Seed — checklist de implementación
| # | Check |
|---|---|
| 1 | User Inés Marzal / coord@pizarra.ayto / password bcrypt de password123 |
| 2 | 6 workshops con slugs de doc 05 y imageKey alineado a assets |
| 3 | Distritos: uno por taller (los 6 del brief) |
| 4 | Requests PZ-0813-001…006 con los 5 estados + un NEW extra |
| 5 | Rosa → barro-usera NEW |
| 6 | Youssef → fogon-carabanchel WAITLIST |
| 7 | Idempotencia: seed borra en orden FK o usa upsert por email/slug/code |
10. Alineación case ↔ app
| Doc | Evidencia esperada en código |
|---|---|
| 05 data model | schema.prisma enums y models |
| 03–04 IA/flows | routes + query params + controllers |
| 08 DS | tailwind colors + fontFamily |
| 09 content | strings en templates (disclaimer ×3) |
| 00 paper | URLs en README case |
| 11 security | JwtAuthGuard en requests GET/PATCH |
11. Notas de cierre técnico
- El case documental L2 está cerrado (docs + Paper ref + contratos).
- Must-have de producto del brief están especificados con AC.
- Mejoras (guards Angular, e2e, rate limit, check aforo) viven en backlog L2+, no como deuda silenciosa.
- Este encargo no modifica Paper, el repo
pizarra-app,memory.mdni el registry.