On this page 00 — Day brief · 2026-08-13 · PIZARRA 0%

00-day-brief.md

00 — Day brief · 2026-08-13 · PIZARRA

Decisiones ALS-2 BRIEF

CampoValor
Fecha2026-08-13
NombrePIZARRA
ComplejidadNivel 2 (no L3 consecutivo tras PRIMA)
SectorEducación cívica / talleres municipales
TipoCatálogo público de talleres + solicitud de plaza + inbox COORD JWT
PlataformaWeb responsive (search-first vecino + desktop coordinación)
RegistroR-CL — clásico institucional excelente (aula municipal, no SaaS)
ShellS-CMD — command palette / search-first
HomeH-SEARCH — la búsqueda es la home
FlujoF-SEARCH — query + facetas → ficha → solicitud
Por qué no PRIMANo S-SIDE / H-DATA / F-KAN; no seguros; no kanban
Por qué no CANTERANo 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

  1. Home search-first: query + chips de disciplina / distrito / precio / accesibilidad
  2. Ficha de taller con foto real, aforo, horario, sede
  3. Formulario público de solicitud (solicitud ≠ plaza confirmada)
  4. Success con código PZ-MMDD-NNN
  5. Login + inbox COORD JWT (estados NEW → CONTACTED → ENROLLED / WAITLIST / CANCELLED)
  6. Empty / loading / error / mobile search
  7. ≥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

IDHipótesisSeñal
H1Search + facetas reduce abandono vs listado PDF/web institucional% sesiones con ≥1 facet + click ficha
H2Copy honesto “solicitud, no plaza” baja expectativas rotas↓ reclamaciones “pensé que estaba inscrito”
H3Inbox con WAITLIST evita overbooking informalENROLLED 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: #E4EAF2 polvo · #1A231E pizarra · #D4A017 tiza · #3F6B5A borrador · #D7D0C2 rail · #EEF2F8 papel
  • Tipo: Newsreader 56/40/28 display · Atkinson 16/14 UI

00-paper-reference.md

Paper reference · PIZARRA

URLhttps://app.paper.design/file/01KZWYWE6BFRD96MAAF8HQWJ78
File ID01KZWYWE6BFRD96MAAF8HQWJ78

UX-count: 12 UI-count: 12

UX process (prefijo UX- · mínimo 12)

IDNombreContenido
UX-00CoverPortada PIZARRA, terna R-CL·S-CMD·H-SEARCH·F-SEARCH, fecha 2026-08-13, L2 cívico, chalky
UX-01StakeholdersVecindad, COORD, centros cívicos, concejalía, monitorado, familia
UX-02PersonasRosa Belmonte (vecina 54) · Inés Marzal (COORD 38)
UX-03JTBDJob “encontrar taller y pedir plaza” + job “contactar solicitudes en 48 h”
UX-04StoriesMust: search, ficha, solicitud, código PZ-, inbox, estados, empty/error
UX-05JourneyQuery → facetas → ficha → solicitud → espera → contacto COORD
UX-06BlueprintFrontstage search/form · backstage llamada · sistemas Nest/Neon
UX-07Site mapPúblico search-first + auth COORD; profundidad 2
UX-08FlujosF-SEARCH: query+chips → ficha → POST → success; F-COORD inbox
UX-09Datos + permisosWorkshop · SeatRequest · User COORD; JWT en list/patch
UX-10MétricasNorth star % válidas CONTACTED ≤ 48 h; facetas; abandono
UX-11Research notesComprobado / supuesto / hipótesis / decisión — sin fake field

UI producto (prefijo UI- · mínimo 10)

IDNombreFlujo
UI-01Home searchH-SEARCH: query + chips disciplina/distrito/precio/a11y + resultados
UI-02Taller detalleFicha foto real, aforo, horario, sede, CTA solicitar
UI-03Solicitar plazaForm público; disclaimer solicitud ≠ plaza confirmada
UI-04SuccessAcuse + código PZ-MMDD-NNN + próximos pasos
UI-05Login COORDAcceso JWT al inbox de coordinación
UI-06Inbox COORDLista + stats NEW/CONTACTED/ENROLLED/WAITLIST/CANCELLED
UI-07Detalle solicitudContacto, taller, aforo vs demanda, acciones de estado
UI-08Empty searchCero resultados; sugerir quitar faceta / otra query
UI-09ErrorFallo de red / API; reintentar
UI-10LoadingSkeleton resultados / inbox
UI-11Design systemTokens chalky, tipo Newsreader + Atkinson, badges, search field
UI-12Mobile searchHome 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)

§BandaPropósitoArtboards clave
1UX PROCESSModelo de servicio cívicoUX-00…UX-11
2DESIGN SYSTEMTokens chalky + componentes searchUI-11
3PUBLIC SEARCHCara vecina search-firstUI-01, UI-02, UI-03, UI-04, UI-08, UI-12
4COORDFlujos autenticadosUI-05, UI-06, UI-07
5STATESResilienciaUI-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

ArtboardRuta appComponente
UI-01 Home search/SearchPage
UI-02 Taller detalle/taller/:slugWorkshopPage
UI-03 Solicitar plaza/solicitar/:slugRequestPage
UI-04 Success/okSuccessPage
UI-05 Login COORD/loginLoginPage
UI-06 Inbox COORD/inboxInboxPage
UI-07 Detalle solicitud/inbox/:idRequestDetailPage
UI-08 Empty search/?q=… 0 hitsempty en home
UI-09 Errorsearch / inboxbanner error + retry
UI-10 Loadingsearch / inboxskeletons
UI-11 Design systemtokens Tailwind
UI-12 Mobile search/ viewport 390mismo HomeSearchPage

3. Tokens de diseño en Paper

TokenValorUso
Polvo / bg#E4EAF2Fondo de página
Papel#EEF2F8Surfaces, cards
Pizarra#1A231ETexto fuerte, CTAs dark
Tiza#D4A017Acento search, focus, highlights
Musgo#3F6B5AAcciones positivas, ENROLLED
Rail#D7D0C2Bordes, chips inactivos
DisplayNewsreader 56/40/28Titulares, wordmark
UIAtkinson Hyperlegible 16/14Body, labels, search, forms

4. Checklist de densidad (anti thin-frames)

CriterioUXDSPublic searchCOORDStates
Jerarquía tipográfica visible
Microcopy real (no lorem)
Tokens chalky aplicados
Datos de seed creíblesPersonas6 talleres + fotosCódigos PZ-Empty realista
Media / iconografíaCoverFotos tallerBadges estadoSkeletons

5. Media

  • assets/hero.jpg
  • assets/taller-ceramica.jpg
  • assets/taller-escritura.jpg
  • assets/taller-teatro.jpg
  • assets/taller-cocina.jpg
  • assets/taller-robotica.jpg
  • assets/taller-musica.jpg

6. Enlaces

01-project-definition.md

01 — Definición de proyecto — PIZARRA

1. Identidad

CampoValor
NombrePIZARRA
SignificadoLa pizarra del aula municipal: oferta visible, tiza, cupo y nombre
Eslogan”La plaza se pide con nombre, no con un PDF.”
Una fraseCatálogo search-first de talleres municipales + solicitud pública de plaza + inbox JWT de coordinación.
SectorEducación cívica / talleres municipales
TipoWeb cívica search-first + SeatRequest + inbox COORD — Nivel 2
PlataformaWeb responsive (móvil-first vecino + desktop coordinación)
Mercado demoEspaña · Madrid · distritos Usera, Vallecas, Latina, Carabanchel, Villaverde, Arganzuela
Idiomaes-ES
Fecha caso2026-08-13
TernaR-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

ProblemaQuién lo sufreEfecto
Oferta enterrada en PDF / mega-listadoVecina (Rosa)Abandono antes de encontrar el taller
“¿Quedan plazas?” por teléfonoAmbosCola informal, overbooking
Mensaje enviado sin acuseVecinaNo sabe si llegó; llama de nuevo
Excel / correo sin estadoCOORD (Inés)Se pierde quién ya fue contactado
Expectativa de “ya estoy inscrita”AmbosReclamaciones 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

IDHipótesisSeñal de validación (futura)
H1Search + facetas reduce abandono vs listado PDF/web institucional% sesiones con ≥1 facet + click ficha
H2Copy honesto “solicitud, no plaza” baja expectativas rotas↓ reclamaciones “pensé que estaba inscrito”
H3Inbox con WAITLIST evita overbooking informalENROLLED 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

ParaValor
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íaCanal digital mínimo: oferta visible + pipeline de contacto, sin portal de matrícula completo.

No es PIZARRA

ExcluidoPor qué
LMS / aula virtual / entregasJob distinto (enseñar, no asignar plaza)
Pagos, tasa online, pasarelaInscripción administrativa; tasa informativa
Marketplace de monitores privadosOferta municipal, no matching
CRM multi-concejalía / CITA previa sanitariaSobre-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

RolObjetivo medible en demo
VecinaEncontrar un taller en < 60 s (query o chip) y enviar solicitud en < 2 min
COORDMarcar 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ónPúblicoCOORD
Buscar / filtrar talleres
Ver ficha de taller
POST solicitudSí (mismo form)
Login JWTNo (no cuenta vecina)
Listar solicitudesNo (401)
Ver stats summaryNo
Ver detalle solicitudNo
Cambiar statusNo
Crear / editar WorkshopNo (seed)No en v1

6. Métricas (modelo, no instrumentadas en v1 salvo base)

TipoMétricaDefinición
North Star% solicitudes válidas CONTACTED ≤ 48 hCalidad operativa del contacto, no “matrículas vendidas”
Activación1ª solicitud públicaPOST create 2xx
Search% sesiones con ≥1 faceta + click fichaH1
ExpectativaReclamaciones “ya inscrito”H2 (cualitativa / tickets)
IntegridadENROLLED ≤ seats por tallerH3
SaludError rate API search/create/list4xx/5xx

7. Alcance funcional v1 (L2)

MóduloIncluido
Home searchQuery + chips disciplina / distrito / precio / accesibilidad + resultados
Catálogo6 talleres seed con foto real, aforo, horario, sede
FichaDetalle + CTA solicitar + disclaimer
SolicitudForm + POST /api/requests
SuccessCódigo PZ-MMDD-NNN + próximos pasos
AuthPOST /api/auth/login → JWT COORD
InboxGET list, GET stats, GET by id, PATCH status
Estados UIEmpty search, loading skeleton, error de red
Mobile searchMisma home a ~390px
Seed1 COORD, 6 workshops, 6 seat requests

8. Criterios de aceptación de producto

  1. Un visitante puede buscar y filtrar talleres sin autenticación y abrir una ficha.
  2. Un visitante puede enviar una solicitud sin cuenta y recibir un código PZ-….
  3. La UI dice explícitamente que la solicitud no confirma plaza.
  4. Sin token, GET/PATCH /api/requests* responden 401.
  5. GET /api/workshops acepta query q y facetas (discipline, district, price, accessibility).
  6. Un COORD puede iniciar sesión y ver el inbox ordenado (createdAt desc) con stats.
  7. El detalle permite transicionar NEW | CONTACTED | ENROLLED | WAITLIST | CANCELLED.
  8. La home es la búsqueda: no hay hero 2-col + 3 cards como superficie principal.
  9. Distritos cubiertos en seed: Usera, Vallecas, Latina, Carabanchel, Villaverde, Arganzuela.
  10. Fotos de taller reales en fichas (assets del case).

9. Stack y artefactos

CapaDetalle
FrontendAngular + Tailwind · puerto 4200
BackendNestJS · puerto 3013
DBNeon PostgreSQL · Prisma · project crimson-voice-36926751
AuthJWT (passport/strategy en API)
DiseñoPaper 01KZWYWE6BFRD96MAAF8HQWJ78
Repo app/Users/cristian/orca/pizarra-app/ · GitHub Criscode2022/pizarra-app

10. Riesgos y mitigaciones

RiesgoImpactoMitigación v1
Spam en form públicoInbox ruidosoValidación server; honeypot / rate-limit en L2+
Expectativa de matrícula instantáneaReclamación y cupo fantasmaCopy “solicitud ≠ plaza”; estado ENROLLED solo COORD
OverbookingConflicto en aulaWAITLIST + regla H3 documentada; COORD no debe ENROLLED > seats
Confundir con “cursos online”Expectativa LMSCopy presencial, sede, horario de aula
PII de vecindad en solicitudesPrivacidad municipalSolo COORD lista; doc 11
Search vacío frustranteAbandonoEmpty state con “quita un filtro” + chips populares
Home percibida como portal institucional genéricoPérdida de craftChalky + Newsreader; search como superficie

11. Glosario

TérminoDefinición en PIZARRA
TallerWorkshop publicado: disciplina, distrito, sede, horario, aforo
SolicitudSeatRequest; no es plaza confirmada
Plaza / inscripciónSolo cuando COORD marca ENROLLED
Lista de esperaWAITLIST cuando seats están cubiertos o hay duda de cupo
COORDUsuario autenticado de coordinación de distrito / programa
Código PZ-Identificador corto oral (PZ-0813-001)
FacetaFiltro de catálogo: disciplina, distrito, precio, accesibilidad
TasaPrecio informativo (gratuito o importe); no cobro in-app

12. Decisiones de diseño (cierre de brief)

IDDecisiónAlternativa descartada
D1Home = search (H-SEARCH)Hero 2-col + 3 cards (AP-12)
D2Shell command/search (S-CMD)Sidebar CRM o bottom-nav wizard
D3Registro clásico institucional (R-CL)SaaS slate/teal o “wellness”
D4Flujo F-SEARCHWizard de onboarding o kanban
D5Mood chalky (pizarra de aula)Bookish biblioteca (primer instinto)
D6Un rol autenticado COORDMulti-rol monitor / concejalía
D7Catálogo Workshop de 1ª claseEnum 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)

ObjetivoMétodo en este casoSalida
Entender actores del programa municipalModelado de stakeholders + personas§3–4
Definir job de búsqueda y de contactoJTBD + stories Must§5
Mapear fricción PDF → plazaJourney + service blueprint§6–7
Traducir a requisitos L2 search-firstMatriz 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

ProhibidoPermitido
“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 realQuotes de persona etiquetadas como constructo de diseño
SLA medido “48 h en Usera” como KPI realNorth star modelo “CONTACTED ≤ 48 h”
Aforo real de un centro concretoAforos de seed de producto

3. Stakeholders

StakeholderInfluenciaInterésNecesidad principal
Vecina / vecino participanteBaja formalMuy altaEncontrar taller y dejar solicitud con acuse
COORD de programa (Inés)AltaMuy altaCola con estado y código oral
Familia / acompañanteMediaAltaHorario, sede, accesibilidad claros
Monitor / talleristaMediaMediaGrupo cerrado a tiempo, sin overbooking
Dirección del centro cívicoAlta adopciónMediaMenos mostrador saturado el día de inscripción
Concejalía de cultura / educaciónAlta políticaMediaOferta visible, no portal electoral
Web institucional del AyuntamientoCompetencia / canal paraleloNo 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

CampoDetalle
Edad / contexto54 años; Usera; busca cerámica o escritura cerca, preferible tarde
DigitalMedia; móvil grande; usa Google; evita “sedes electrónicas” con certificado
GoalsVer si hay taller cerca, si es accesible, si queda hueco; dejar datos y un código
PainsPDFs, 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 demorosa.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

CampoDetalle
Edad / contexto38 años; coordinación de talleres en distrito; parte del día en centro, parte en mesa
DigitalMedia–alta; desktop para la cola; móvil para devolver llamadas
GoalsVer NEW del día, contactar en 48 h, inscribir o pasar a WAITLIST sin Excel
PainsCorreo 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 democoord@pizarra.ayto / password123

Escenario: Login → inbox → Rosa NEW → llama → CONTACTED → si hay silla ENROLLED; si el grupo está lleno WAITLIST.

Anti-personas

QuiénPor qué no es target v1
Alumno de campus online que entrega ejerciciosEso es LMS, no catálogo de plaza
Gestor de contratas / facturación de monitoresBackoffice de proveedores, otro producto
Turista de “experiencias” tipo ClassPassMarketplace, 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

JobRol
Filtrar por accesibilidad (rampa, bucle, planta baja)Vecina
Referir una solicitud por código corto en ventanillaAmbos
Ver cuántas NEW / WAITLIST hay hoyCOORD
Cancelar duplicado o desistimientoCOORD

Stories Must (v1)

IDStoryAC
US1Como vecina, quiero buscar por texto y chipsGET workshops con q + facetas; resultados o empty
US2Como vecina, quiero abrir la ficha con aforo, horario y sedeGET :slug; foto real; CTA
US3Como vecina, quiero enviar solicitud sin cuentaPOST 201 + redirect success
US4Como vecina, quiero un código y saber que no estoy inscritacode en /ok + copy disclaimer
US5Como COORD, quiero entrar con email/passwordJWT + redirect /inbox
US6Como COORD, quiero listar solicitudes y statsGET list + summary
US7Como COORD, quiero cambiar estado incl. WAITLISTPATCH status
US8Como cualquiera, quiero ver empty / error / loadingUI-08, UI-09, UI-10

MoSCoW (v1 L2)

PrioridadÍtems
MustSearch home, ficha, form, success, login, inbox, detail, 5 estados, empty/loading/error, mobile search
ShouldChips activos visibles; aforo vs nº solicitudes en detalle COORD; labels ES de enums
CouldPersistencia de query en URL; click-to-call; copiar código al portapapeles
Won’tPagos, cuenta vecina, CMS talleres, email transaccional, calendario iCal

6. Journey (vecina → COORD)

FaseActorAcciónTouchpointEmoción [HIPÓTESIS]
1 PreguntarRosaEscribe “cerámica” o toca chipHome searchEsperanza cautelosa
2 AcotarRosaChip Usera / gratuito / planta bajaFacetasControl
3 EntenderRosaLee ficha: martes, aforo 12, sede/talleres/barro-useraConfianza o duda de cupo
4 PedirRosaForm + disclaimer/solicitarPrudencia
5 AcuseRosaVe PZ-0813-001/okAlivio (no euforia de “inscrita”)
6 ContactoInésVe NEW, llamaInbox + detalleControl operativo
7 CierreInésENROLLED o WAITLISTPATCHCierre honesto

Momentos de verdad

  1. Search en el primer viewport — si hay que “descubrir el catálogo”, se rompe S2 [HIPÓTESIS].
  2. Disclaimer en form y success — si falta, H2 falla.
  3. Código PZ- — prueba de “ha llegado” para ventanilla y llamada.
  4. WAITLIST visible — evita la mentira social del “ya te apunto”.

7. Service blueprint (resumen)

CapaElementos
Frontstage vecinaSearch, resultados, ficha, form, success
Frontstage COORDLogin, inbox, detalle, botones de estado
BackstageLlamada / SMS / mostrador (fuera de app)
SistemasNest API, Prisma, Neon, JWT, Angular search
SoportesSeed 6 talleres + fotos, Paper, docs
Fallos0 resultados; 401 sin token; 404 slug; red caída → UI error

Fallos de servicio y respuesta de diseño

FalloEvidencia de UIRecuperación
Query sin hitsUI-08 Empty search“Prueba sin distrito” + chips sugeridos
API caída en searchUI-09 ErrorReintentar
Latencia catálogoUI-10 LoadingSkeleton de cards
Form inválidoInline fieldNo navegar a success
Cupo lleno (ops)COORD ve seats vs countForzar WAITLIST, no ENROLLED [H3]

8. Matriz hallazgo → requisito → feature

HallazgoTipoRequisitoFeature v1
Llega con pregunta, no con marca[SUPUESTO] S2Search es la homeUI-01 / H-SEARCH
PDF / listado no filtra[HIPÓTESIS] H1Facetas disciplina/distrito/precio/a11yQuery params + chips
Sin acuse[HIPÓTESIS]Código oralSuccess + PZ-
Confunden solicitud con plaza[HIPÓTESIS] H2Copy irrenunciableDisclaimer form/success
Overbooking informal[HIPÓTESIS] H3Estado WAITLIST + seatsEnum + campo Workshop.seats
PII de vecindad[DECISIÓN]Auth JWT COORDLogin + guards API
L2 compacto, no LMS[DECISIÓN]Un rol COORD; Workshop seedSin CMS, sin cuenta vecina

9. Preguntas abiertas (no bloquean v1)

IDPreguntaCó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étodoMuestra orientativaPregunta
Test de usabilidad search5–6 vecinas 45–70¿Encuentran taller en < 60 s?
Shadowing COORD 1 jornada1–2 centros¿El inbox sustituye el Excel del día?
Revisión de reclamacionesTickets “ya inscrito”Validar H2
Card sort de disciplinas6–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

PrincipioAplicación
Search firstLa home es el buscador + resultados. No hay landing de marca por delante.
Pregunta → ficha → solicitudProfundidad 2 desde query hasta form
Público vs COORDCatálogo y form abiertos; inbox solo autenticado
Lenguaje de dominioTaller, plaza, solicitud, distrito, sede — no “curso SKU”, “lead CRM”, “ticket”
Código visiblePZ-… en success e inbox para referencia oral / ventanilla
Honestidad de estadoSolicitud ≠ 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

AudienciaNodos relevantes
VecinaHome search → Ficha → Solicitar → Ok
COORDLogin → Inbox → Detail (y catálogo público si consulta oferta)
AmbosWordmark → / (COORD autenticada: wordmark puede ir a /inbox)

3. Navegación

Pública (S-CMD)

ElementoDestinoNotas
Wordmark PIZARRA/Newsreader; no “portal del Ayuntamiento”
Campo de búsqueda/ (focus / submit)Superficie principal; placeholder de query
Chips de faceta/ + query paramsDisciplina, distrito, precio, accesibilidad
Card de taller/talleres/:slugResultado
Solicitar plaza/talleres/:slug/solicitarPrimary en ficha
Coordinación/loginEntrada 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)

ElementoDestinoVisibilidad
Wordmark / Inbox/inboxCOORD
Badge rolCOORD
NombreInés MarzalCOORD
Catálogo/Opcional, para consultar oferta
Salirlimpia token → /loginCOORD
Card solicitud/inbox/:idCOORD
Volver (detalle)/inboxCOORD

No hay sidebar multi-sección en v1 (evita IA de CRM / S-SIDE de PRIMA).

4. Inventario de contenido

PantallaContenidos
Home searchWordmark, campo query, chips, recuento, grid de cards (foto, título, distrito, horario, precio, a11y), empty, loading
FichaHero foto, título, disciplina, distrito, sede, horario, aforo, precio, accesibilidad, descripción, disclaimer, CTA
SolicitarTítulo taller, disclaimer, campos form, submit
SuccessMensaje, código PZ-, “no es plaza”, siguiente paso
LoginTítulo panel coordinación, email, password, submit, error
InboxStats (total, open, byStatus), lista, empty, error
DetailCódigo, vecina, taller, aforo vs demanda, notas, acciones estado

5. Taxonomía

RequestStatus

Status APILabel UISemántica
NEWNuevaAcaba de llegar; sin contacto
CONTACTEDContactadaCOORD inició llamada / mensaje
ENROLLEDInscritaPlaza administrativa cerrada
WAITLISTLista de esperaCupo cubierto o reserva de hueco
CANCELLEDCanceladaDesiste / 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

APILabel UITalleres seed
CERAMICSCerámicaBarro de Usera
WRITINGEscrituraPalabra en Vallecas
THEATRETeatroEscena Latina
COOKINGCocinaFogón Carabanchel
ROBOTICSRobóticaBits Villaverde
MUSICMúsicaCoro Arganzuela

District

APILabel UI
USERAUsera
VALLECASVallecas
LATINALatina
CARABANCHELCarabanchel
VILLAVERDEVillaverde
ARGANZUELAArganzuela

PriceBand (faceta)

APILabel UICriterio seed
FREEGratuitofeeCents = 0
FEECon tasafeeCents > 0 (importe en ficha)

AccessibilityTag (faceta, multi)

APILabel UISemántica seed
STEP_FREESin escalonesPlanta baja / rampa
LOOPBucle magnéticoSala con bucle
SEATEDOpción sentadaAlternativa a suelo / movimiento
LARGE_PRINTLetra grandeMaterial 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 mentalRepresentació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
ParamEjemploEfecto
qceramicaBúsqueda texto (título, sede, descripción)
disciplineCERAMICSFaceta disciplina
districtUSERAFaceta distrito
priceFREEFaceta precio
accessSTEP_FREEFaceta 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

UIAPI
Home resultadosGET /api/workshops?q&discipline&district&price&access
FichaGET /api/workshops/:slug
Submit solicitudPOST /api/requests
LoginPOST /api/auth/login
Inbox listGET /api/requests
StatsGET /api/requests/stats/summary
DetailGET /api/requests/:id
Cambiar estadoPATCH /api/requests/:id/status

9. Decisiones de IA descartadas

IdeaPor qué no en L2 v1
/explorar editorial + search secundarioRompe H-SEARCH
Área “mis solicitudes” por email mágicoCuenta de facto; authz delicada
Calendario mensual de todos los centrosRuido; S2 es query, no month-view
Nested nav “Cultura → Distritos → Centros → …”Sede electrónica
Kanban de solicitudesF-KAN es de PRIMA; aquí la cola es inbox
Wizard de 4 pasos de inscripciónF-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
PasoAcciónSistema
1Aterriza; el search es el primer foco visualRender home
2Query y/o chips; URL se actualizaRouter query params
3Ve resultados con foto, distrito, horarioGET workshops
4Abre ficha o refina facetasNavegació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

CasoComportamiento
0 resultadosUI-08: “Ningún taller con esos filtros” + quitar última faceta
Red / 5xxUI-09 + Reintentar
Primera cargaUI-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
PasoAcciónSistema
1Lee oferta presencialWorkshop
2Contrasta aforo y tags a11yCampos seats, accessTags
3Decide pedir o volver a resultadosRouter

É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-…
CampoValidación cliente (mín.)API
workshopIdrequired (de la ruta)existe en DB
fullNamerequired, min 2@MinLength(2)
emailrequired, email@IsEmail()
phoneoptional@IsOptional()
residentDistrictoptional enum District@IsOptional() @IsEnum
notesoptionaldefault ""
accessNeedoptional texto libre@IsOptional()

Éxito: registro status=NEW, código PZ-MMDD-NNN, pantalla success.
No hay cuenta de vecina en v1.

Errores

CasoComportamiento
Validación DTO400 + mensaje de campo
workshopId inexistente400 / 404
Red caídaError de red en form; no navegar a /ok
Email mal formado400
Doble submitBotó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
PasoAcciónSistema
1Lee acuse y códigoUI (query/state)
2Conserva el código (foto, papel, nota)Fuera de app
3Espera llamada / SMS de coordinaciónFuera 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
CampoValidación clienteAPI
emailrequired, email@IsEmail()
passwordrequired, min 6@MinLength(6)

Éxito: token guardado; user role=COORD, nombre Inés Marzal.
Credencial demo: coord@pizarra.ayto / password123.

CasoComportamiento
Credenciales inválidas401 + mensaje
Red caídaError de red en UI
Token caducado en inbox401 en GET → re-login

F6 — Inbox + stats (COORD)

/inbox (token en cliente)
  → GET /api/requests
  → GET /api/requests/stats/summary
  → Render cards + métricas
CasoComportamiento
Lista con itemsCards: code, nombre, taller, distrito, status, createdAt
Lista vacíaEmpty: “No hay solicitudes todavía.”
Fallo red / 401Error + reintentar / re-login
Statstotal, 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)

DesdeHaciaIntención
NEWCONTACTEDInés inició contacto
CONTACTEDENROLLEDHay silla; inscripción administrativa
CONTACTEDWAITLISTCupo cubierto / pendiente de baja
NEWWAITLISTGrupo ya lleno al abrir la solicitud
*CANCELLEDDesiste, duplicado, no localizable
WAITLISTENROLLEDSe libera silla
ENROLLEDCANCELLEDBaja 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ódigoCuándoUI
400DTO inválidoMensaje campo / genérico
401Sin/mal tokenRe-login
404id o slug no existeMensaje + volver
5xx / networkAPI caídaError + 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:

EntidadPropósito
UserIdentidad de coordinación (rol COORD)
WorkshopTaller publicado (oferta)
SeatRequestSolicitud de plaza ligada a un taller

Base: PostgreSQL (Neon) · ORM: Prisma · IDs: cuid().

2. Enums

Role

ValorDescripción
COORDOperadora del inbox; único rol autenticado v1

RequestStatus

ValorDescripción
NEWRecién creada por el form público
CONTACTEDCOORD ha iniciado contacto
ENROLLEDPlaza administrativa confirmada
WAITLISTEn espera de silla
CANCELLEDAnulada

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

CampoTipoConstraints
idStringPK, cuid
emailStringunique
passwordHashStringbcrypt
nameString
roleRoledefault COORD
createdAtDateTimedefault now
updatedAtDateTimeupdatedAt

Workshop

CampoTipoConstraints
idStringPK, cuid
slugStringunique, kebab-case
titleString
summaryStringcard (~140)
descriptionStringficha
disciplineDisciplineenum
districtDistrictenum
venueStringnombre del centro
addressStringdirección corta
scheduleLabelString“Martes 18:00–20:00”
startsOnDateTimeinicio de edición
endsOnDateTimefin de edición
seatsIntaforo > 0
feeCentsInt0 = gratuito
accessTagsString[]tags a11y
imageKeyStringfilename en assets
createdAt / updatedAtDateTime

SeatRequest

CampoTipoConstraints
idStringPK, cuid
codeStringunique, formato PZ-MMDD-NNN
workshopIdStringFK Workshop
fullNameString
emailString
phoneString?opcional
residentDistrictDistrict?opcional
notesStringdefault ""
accessNeedString?texto libre (no sustituye tags del taller)
statusRequestStatusdefault NEW
createdAt / updatedAtDateTime

5. Reglas de integridad y negocio

ReglaImplementación
Create públicoPOST /api/requests sin guard
List/detail/statusJwtAuthGuard → 401 sin token
Catálogo públicoGET /api/workshops y :slug sin guard
Código únicocode unique; PZ-MMDD- + secuencia 001–999 del día [DECISIÓN] (fallback random si colisión)
Status defaultNEW al crear
PasswordNunca en claro; solo passwordHash
NotasCoalesce a "" si omitidas
Orden listado solicitudescreatedAt desc
Orden catálogodistrict asc, title asc (estable; no “destacados”)
Search textocontains case-insensitive en title, summary, venue
FacetasAND entre params; access = has tag
ENROLLED vs seatsAviso 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

CampoTipoReq
workshopIdstring
fullNamestring
emailstring
phonestringno
residentDistrictDistrictno
notesstringno
accessNeedstringno

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

NombreEmailPassword
Inés Marzalcoord@pizarra.aytopassword123

Workshops

slugTítuloDisciplinaDistritoSede (demo)HorarioSeatsTasaA11yAsset
barro-useraBarro de UseraCerámicaUseraC.C. UseraMar 18:00–20:00120STEP_FREE, SEATEDtaller-ceramica.jpg
palabra-vallecasPalabra en VallecasEscrituraVallecasC.C. Puente de VallecasJue 17:30–19:30160LOOP, LARGE_PRINTtaller-escritura.jpg
escena-latinaEscena LatinaTeatroLatinaC.C. AlucheMié 19:00–21:00141500STEP_FREEtaller-teatro.jpg
fogon-carabanchelFogón CarabanchelCocinaCarabanchelC.C. San FranciscoSáb 11:00–13:00102000SEATEDtaller-cocina.jpg
bits-villaverdeBits VillaverdeRobóticaVillaverdeC.C. San CristóbalVie 17:00–19:00120STEP_FREEtaller-robotica.jpg
coro-arganzuelaCoro ArganzuelaMúsicaArganzuelaC.C. LegazpiLun 19:00–20:30200LOOP, LARGE_PRINTtaller-musica.jpg

Nombres de centros y horarios son contenido de producto demo, no programación oficial publicada [SUPUESTO de catálogo].

SeatRequests

CódigoNombreTallerEstadoNotas demo
PZ-0813-001Rosa Belmontebarro-useraNEW“Prefiero mesa baja; vengo de Pradolongo.”
PZ-0813-002Luis Arandapalabra-vallecasCONTACTED
PZ-0813-003Marta Gilcoro-arganzuelaENROLLED
PZ-0813-004Youssef Amranifogon-carabanchelWAITLISTGrupo lleno en mostrador
PZ-0813-005Pilar Sotoescena-latinaCANCELLEDDuplicada
PZ-0813-006Carmen Ruizbits-villaverdeNEWHija 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)

CambioNivel
coordDistrict en User + filtro de colaL2+
Check DB ENROLLED < seatsL2+
Tabla Venue / Center normalizadaL3
Soft delete + audit log de statusL2+
CMS Workshop autenticadoL3
User VECINA con historialL3

06-tech-stack.md

06 — Stack tecnológico — PIZARRA

1. Visión

CapaTecnologíaNotas
FrontendAngular standalone + signalsPuerto 4200
EstilosTailwind CSSTokens chalky (bg/ink/chalk/moss)
BackendNestJS TypeScriptPuerto 3013, prefijo /api
ORMPrismaWorkshop, SeatRequest, User
DBNeon PostgreSQLproject crimson-voice-36926751
AuthJWT passportRol COORD
DiseñoPaper01KZWYWE6BFRD96MAAF8HQWJ78
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étodoRutaAuth
GET/api/workshopsPúblico + query/facetas
GET/api/workshops/:slugPúblico
POST/api/requestsPúblico
POST/api/auth/loginPúblico
GET/api/requestsJWT COORD
GET/api/requests/stats/summaryJWT
GET/api/requests/:idJWT
PATCH/api/requests/:id/statusJWT

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

RolHexReferente
Ground#E4EAF2Polvo de tiza
Surface#EEF2F8Papel de lista
Ink#1A231EPizarra verde-negra
Muted#4A564FFieltro del rail
Accent#D4A017Tiza amarilla
Secundario#3F6B5ABorrador musgo
Rail#D7D0C2Listó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

  1. La búsqueda es el monumento, no un hero 2 columnas.
  2. Radio 6–10px. Sin píldoras atléticas.
  3. Foto real del taller, nunca color sólido.
  4. Copy honesto: solicitud ≠ plaza.
  5. 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.

TokenValor
bg#E4EAF2
surface#EEF2F8
ink#1A231E
ink-muted#4A564F
chalk#D4A017
moss#3F6B5A
rail#D7D0C2
danger#9B3A2F
displayNewsreader
sansAtkinson Hyperlegible
radius6 / 10 / 16

Componentes

PiezaUso
Command barHome: input 20px + CTA tiza
Chip / facetOn = ink+blanco; off = surface+rail
Search hitFoto 168×112 + meta + título + plazas
Button primaryFondo chalk, texto ink
Button secondaryFondo ink, texto blanco
Button ghostBorde ink
Badge NEWchalk
Badge WAITLISTink
Badge ENROLLEDmoss
Form fieldBorde ink 1px, label 13px bold
DisclaimerFondo #E8D48A
Skeletonrail 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

UsarNo usar
Solicitud de plazaReserva confirmada, inscripción automática
Coordinación te contacta«Ya estás dentro»
Lista de esperaOverbooking
TallerCurso online, masterclass
Distrito / sedeCampus
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

TemaDecisión
Tipo UIAtkinson Hyperlegible
Cuerpo≥16px
ContrasteInk #1A231E sobre #E4EAF2; chalk+ink para CTA
FocoOutline 2px ink visible
Facetasfieldset + legend, no solo chips mudos
Imágenesalt descriptivo del taller
FormLabels visibles, no placeholder-only
ErroresTexto, no solo color
MobileTargets ≥44px en chips y CTA
MovimientoSin 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 #D4A017 solo 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)

  1. Navegar home → ficha → form solo teclado.
  2. Lighthouse a11y ≥ 90 en search.
  3. Contraste de chips on/off.
  4. Lector: legend de disciplina anunciado.

11-privacy-security.md

11 — Privacidad y seguridad — PIZARRA

No es un dictamen legal.

Datos

DatoClasificaciónUso
Nombre, email, teléfonoPIIContactar solicitud
NotasPII opcionalContexto COORD
Taller, estado, códigoOperativoCola y aforo
Password hashSecretoSolo 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

EventoDóndeProps
search_submittedHomeq, result_count
facet_toggledHomekey, value
workshop_openedHit / fichaslug
request_startedFormslug
request_submittedPOST OKcode, waitlist
request_failedPOST errorreason
coord_loginLogin
status_changedInbox detallefrom, 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)

  1. GET /api/workshops → ≥6 ítems.
  2. POST /api/requests con slug real → código PZ-.
  3. POST /api/auth/login coord@pizarra.ayto / password123 → accessToken.
  4. GET /api/requests Bearer → incluye el POST.
  5. ng build OK.

Casos

IDCasoEsperado
S1Query «cerámica»Solo CERAMICA
S2Faceta UseraSolo Usera
S3Taller llenoPOST status WAITLIST
S4Email inválido400, form error
S5SuccessCódigo visible, copy no inscrito
S6Inbox sin JWT401 / redirect login
S7ENROLLED 2 vecesseatsTaken +1 una vez
S8EmptyCTA quitar filtros
S9API caídaError + 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

  • 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.

RutaPaperRol
/UI-01, UI-08, UI-09, UI-10, UI-12Vecina
/taller/:slugUI-02Vecina
/solicitar/:slugUI-03Vecina
/okUI-04Vecina
/loginUI-05COORD
/inboxUI-06COORD
/inbox/:idUI-07COORD

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

RequisitoEstadoEvidencia
Diversidad sector/tipo/nivelOKCívico L2; no kanban; no wizard; no LMS
Terna ≥2 códigos vs N−1/N−2OKR-CL · S-CMD · H-SEARCH · F-SEARCH (brief)
Day brief + anti-patronesOKdocs/00-day-brief.md (no sobrescrito)
Paper ≥12 UX + ≥10 UIOKUX-count 12 · UI-count 12 · file 01KZWYWE6BFRD96MAAF8HQWJ78
Docs 00–20OKsuite en docs/ + executive
JWT COORDOKRole COORD · D-P1-05
API + seed + NeonOKcrimson-voice-36926751, port 3013 (contratos)
Web tokensOKNewsreader/Atkinson · paleta chalky documentada
Search-first (no AP-12)OKUI-01 / docs 03, 07, 08
Hipótesis no fake fieldOKetiquetas en doc 02
Copy solicitud ≠ plazaOKdocs 01, 09, 16

3. Cobertura funcional

Feature briefSpecUI PaperAPIDocs
Home search + chipsUI-01, UI-12GET workshops03, 04, 16
Ficha taller + fotoUI-02GET :slug05, 09
Form solicitudUI-03POST requests04, 05
Success + PZ-UI-04code04, 09
Login COORDUI-05POST login04, 06
Inbox + statsUI-06GET + summary05
Detail + 5 estadosUI-07GET + PATCH04
Empty searchUI-08200 []16
ErrorUI-095xx/red16
LoadingUI-10pending16
Design systemUI-1108
Seed 6+6seed05

4. Cobertura Paper

IDNombreEn 00-paper-reference
UX-00Cover
UX-01Stakeholders
UX-02Personas
UX-03JTBD
UX-04Stories
UX-05Journey
UX-06Blueprint
UX-07Site map
UX-08Flujos
UX-09Datos + permisos
UX-10Métricas
UX-11Research notes
UI-01…12Search → mobile

5. Rúbrica de calidad (auto SCORE orientativo)

EjeScore 1–5Comentario
Diversidad5R-CL recuperado; search-first nuevo vs PRIMA/CANTERA
Craft visual4–5Chalky + Newsreader + fotos taller (Paper/app)
Densidad UX docs5Suite L2 completa, etiquetada
Completitud código L2App fuera de este encargo de documentación
Authz5Contrato: guard en list/detail/status
Verdad investigación5Sin stats de campo falsas

6. Huecos aceptados (no regresiones de cierre documental)

HuecoClasificación
Pagos / LMS / Cl@veFuera L2
e2e automatizadoL2+
Route guards formales AngularL2+
Check DB aforoL2+ / L3
CMS talleresL3
Checkbox RGPDL2+ 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ÍtemCapa
D01Definición producto PIZARRADocs
D02Day brief diversidad cívica L2Docs (preexistente)
D03Terna R-CL · S-CMD · H-SEARCH · F-SEARCHDocs
D04Personas Rosa / InésDocs + Paper ref
D05JTBD + stories MustDocs
D06IA search-first + query paramsDocs
D07Flujos F1–F8 + ACDocs
D08Modelo Workshop / SeatRequest / UserDocs
D09Contratos API :3013Docs
D10Auth JWT COORDDocs
D115 estados incl. WAITLISTDocs
D12Código PZ-MMDD-NNNDocs
D13Disclaimer solicitud ≠ plazaDocs
D14North star CONTACTED ≤ 48 hDocs
D15Tokens chalky + Newsreader/AtkinsonDocs
D16Paper UX-00…11 + UI-01…12Docs
D17Suite docs 00–20 + README + executiveDocs
D18Seed 6 talleres + 6 solicitudes especificadoDocs
D19Assets listadosREADME
D20QA smoke S1–S18Docs

2. Backlog L2+ (misma complejidad, implementación / polish)

IDÍtemPrioridadNotas
B01canActivate guards AngularP1UX auth
B02Filtros por status en inboxP2Query API
B03Aviso / bloqueo suave ENROLLED ≥ seatsP1H3
B04Captcha / rate limit POSTP1anti-spam
B05Checkbox privacidadP1RGPD
B06Playwright smoke S1–S18P1CI
B07Skeleton UI-10 en códigoP2
B08mailto/tel en detailP2ops
B09Copy code clipboardP3
B10API URL por environmentP1deploy
B11aria-live recuento searchP2a11y
B12Página 404 amigableP3

3. Backlog L3 (requiere brief nuevo)

IDÍtemDependencia
C01COORD por distritoUser.coordDistrict
C02CMS WorkshopRole EDITOR
C03Email transaccionalprovider
C04Lookup público por códigoauthz
C05Check DB aforotransacción
C06Audit logEvent
C07Calendario iCalschedule
C08Ranking empadronamientoregla Q5

4. Backlog explícitamente fuera

IDÍtem
E01LMS / asistencia
E02Pagos tasa
E03Cl@ve / sede electrónica
E04Multi-municipio
E05Kanban solicitudes
E06Cuenta de vecina completa

5. Explicitamente no-backlog

IdeaRazón
Hero 2-col + 3 cardsAP-12 / rompe H-SEARCH
Sidebar CRMS-SIDE de PRIMA
Wizard inscripción 4 pasosF-ONB de CANTERA
Marketplace de monitoresOtro producto

6. Orden de ataque recomendado (si hay continuidad de código)

  1. Implementar contratos docs 05–06 + seed 05
  2. B01 guards + B06 e2e + B10 env
  3. B03 aforo + B08 contact
  4. B04 + B05 si hay tráfico
  5. Evaluar brief L3 distrito / CMS

7. Trazabilidad

OrigenÍtems
Day brief must-haveD01–D20
Doc 15 L2+B01–B12
Doc 15 L3 / fueraC01–C08, E01–E06

20-implementation.md

20 — Implementación — PIZARRA

1. Resumen ejecutivo técnico

CampoValor
App path/Users/cristian/orca/pizarra-app/
APINestJS · puerto 3013 · prefijo /api
WebAngular standalone · puerto 4200
DBNeon PostgreSQL · Prisma · crimson-voice-36926751
AuthJWT Bearer · role COORD
DominioUser, Workshop, SeatRequest
Fecha2026-08-13
GitHubhttps://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

RolEmailPassword
COORDcoord@pizarra.aytopassword123

3. Módulos API a implementar

Auth

  • POST /api/auth/login
  • Valida email/password; compara bcrypt; emite JWT con sub, email, role.
  • JwtStrategy + JwtAuthGuard protegen lectura/escritura de solicitudes.

Workshops (público)

MétodoRutaAuthNotas
GET/api/workshopsNoq, discipline, district, price, access
GET/api/workshops/:slugNo404 si no existe

Filtros: texto contains insensitive en title, summary, venue; facetas AND; price=FREEfeeCents = 0; accessaccessTags has.

Requests

MétodoRutaAuthNotas
POST/api/requestsNocreate; status NEW; code PZ-
GET/api/requestsJWTlist + workshop; createdAt desc
GET/api/requests/stats/summaryJWTtotal, open, waitlist, byStatus
GET/api/requests/:idJWT+ workshop + enrolledCount; 404
PATCH/api/requests/:id/statusJWTbody { 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áginaResponsabilidad
HomeSearchPagequery params, chips, GET workshops, empty/loading/error
WorkshopDetailPageGET slug, foto, disclaimer, CTA
RequestSeatPageform create público + disclaimer
SuccessPageconfirmación + código + “no es plaza”
LoginPageform → ApiService.login → inbox
InboxPagestats + list + empty/error + logout
RequestDetailPageget + 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ónRazón
Puerto API 3013Evitar colisión con otros daily apps
Workshop 1ª claseL2 catálogo; search real
Search en servidorURL compartible; source of truth
Soft auth en páginasSimple; API es autoridad
scheduleLabel stringMenos fricción timezone
Sin FK User–SeatRequestForm anónimo
Código PZ-MMDD-NNNReferencia oral corta
Templates standaloneVelocidad daily
Stats endpoint propioEvolución y menos lógica en cliente
stats/summary antes de :idEvitar captura de param
No bloquear POST por aforoH2/H3: COORD decide WAITLIST
Home no usa hero como layoutAP-12 / H-SEARCH

6. Variables de entorno

VariableServicioDescripción
DATABASE_URLAPINeon crimson-voice-36926751
JWT_SECRETAPIFirma tokens
PORTAPIopcional, 3013

Web: URL de API en ApiService (default localhost:3013).

7. Smoke de implementación (mínimo)

  1. Seed OK (PIZARRA seed OK).
  2. GET workshops length 6.
  3. GET ?q=ceramica&district=USERA devuelve Barro de Usera.
  4. Login COORD 200.
  5. List length ≥ 1.
  6. Create request 201 + code PZ-.
  7. Patch CONTACTED / WAITLIST / ENROLLED 200.
  8. GET requests sin token 401.
  9. 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
1User Inés Marzal / coord@pizarra.ayto / password bcrypt de password123
26 workshops con slugs de doc 05 y imageKey alineado a assets
3Distritos: uno por taller (los 6 del brief)
4Requests PZ-0813-001…006 con los 5 estados + un NEW extra
5Rosa → barro-usera NEW
6Youssef → fogon-carabanchel WAITLIST
7Idempotencia: seed borra en orden FK o usa upsert por email/slug/code

10. Alineación case ↔ app

DocEvidencia esperada en código
05 data modelschema.prisma enums y models
03–04 IA/flowsroutes + query params + controllers
08 DStailwind colors + fontFamily
09 contentstrings en templates (disclaimer ×3)
00 paperURLs en README case
11 securityJwtAuthGuard 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.md ni el registry.