00-day-brief.md
00 — Day brief · 2026-08-20 · PREGÓN
Decisiones ALS-2 BRIEF
| Campo | Valor |
|---|---|
| Fecha | 2026-08-20 |
| Nombre | PREGÓN |
| Complejidad | Nivel 1 (no L2 consecutivo tras MATIZ) |
| Sector | Administración pública local (distrito Usera / Villaverde) |
| Tipo | Chat-first de incidencias vecinas + mesa OPERADOR JWT (kanban Recibido / En curso / Cerrado) |
| Plataforma | Web responsive (chat vecino · mesa desktop) |
| Registro | R-IN — el ayuntamiento se escribe, no se “tramita un formulario” |
| Shell | S-SIDE — rail de estados en mesa; el chat público no lleva top-nav de marketing |
| Home | H-CHAT — la entrada es el hilo, no un hero |
| Flujo | F-KAN — el operador mueve columnas; el vecino no ve el tablero |
| Por qué no MATIZ | No S-BOTTOM / H-BENTO / F-EDIT; no belleza; no PWA de oficio |
| Por qué no ZAGUÁN | No S-WIZ / H-WIZARD / F-CMP; no inmobiliaria |
| Por qué no PIZARRA | Chat ≠ catálogo search; incidencia ≠ plaza de taller |
Terna unicidad
R-IN · S-SIDE · H-CHAT · F-KAN
0 códigos iguales a N−1 (MATIZ: R-HY · S-BOTTOM · H-BENTO · F-EDIT).
≥2 distintos vs N−2 (ZAGUÁN) y N−3 (MARGEN).
Ventana 4: R-HY + R-IN + R-CL + R-CL → hoy R-IN (sigue habiendo clásico en la ventana).
S-SIDE, H-CHAT y F-KAN no están en los últimos 5 (S-BOTTOM/WIZ/TOP/RAIL/DOCK · H-BENTO/WIZARD/GALLERY/TABLE/FEED · F-EDIT/CMP/STORY/LEAD/BOOK).
Ground (CRON §5.6)
- Familia: G-FOG
- Hex:
#F1F3F6 - Por qué: fotocopia fría de un bando, no yeso de capilla (MATIZ), no hormigón (ZAGUÁN), no kiosco blanco (MARGEN), no musgo (CESTA), no cromo (RONDA).
Mood visual (Paper)
- Candidatos: chalky (primer instinto municipal), highlighter, brutalist, maritime, bookish
- Elegido: highlighter — no el primer instinto pizarra (eso fue PIZARRA). Un bando se marca con fluorescente, no con tiza.
- Paleta:
#F1F3F6fotocopia (ground)#12141Atinta de sello#C8F000rotulador fluorescente#FFFFFFficha#5C6370grafito#C1121Flacre (acento de urgente, poco)
- Tipo: Ibarra Real Nova display · Jost UI (evita Libre Baskerville / Nunito / Instrument / Sora / Cormorant / Public Sans / Spectral / Manrope)
Alcance L1 must
- Home chat: hilo de bandos del distrito + compositor de incidencia (calle, tipo, texto)
- Acuse con código (enviado ≠ resuelto)
- Login JWT OPERADOR
- Mesa kanban Recibido / En curso / Cerrado
- Ficha de parte + cambio de columna
- Empty / error / loading / mobile chat
- ≥12 UX + ≥10 UI
Microcopy duro
- Mensaje ≠ expediente. PREGÓN no abre un procedimiento SIA.
- Enviado ≠ resuelto. El acuse no apaga la farola.
- PREGÓN no cita. No hay slot ni “te llamamos a las 10”.
- PREGÓN no es 112. Urgencia vital fuera.
Supuestos
- S1: Un distrito atiende 8–20 partes/día; la demo tiene 7 partes en tres columnas.
- S2: El vecino escribe desde el móvil en la acera; no crea cuenta.
- S3: Un OPERADOR (Nuria) mueve columnas; no hay sede electrónica.
- S4: Tipos: farola, cubo, banco, acera, otro. Sin GIS.
Hipótesis
| ID | Señal |
|---|---|
| H1 | Un chat reduce “no sé qué formulario es” |
| H2 | Separar enviado de resuelto evita la expectativa 112 |
| H3 | Kanban de 3 columnas basta; el vecino no necesita login |
00-paper-reference.md
Paper reference · PREGÓN
| URL | https://app.paper.design/file/01M0EZQ31KF04PAQ5RMCV7DQ2F |
| File ID | 01M0EZQ31KF04PAQ5RMCV7DQ2F |
UX-count: 12 UI-count: 12
UX-00 Cover · UX-01 Stakeholders · UX-02 Personas · UX-03 JTBD · UX-04 Stories · UX-05 Journey · UX-06 Blueprint · UX-07 Site map · UX-08 Flujos · UX-09 Datos+permisos · UX-10 Métricas · UX-11 Research UI-00 Tokens · UI-01 Chat home · UI-02 Chat mobile · UI-03 Composer · UI-04 Success · UI-05 Login · UI-06 Kanban · UI-07 Ficha · UI-08 Empty · UI-09 Error · UI-10 Loading · UI-11 Mesa mobile
UX process (prefijo UX- · mínimo 12)
| ID | Nombre | Contenido |
|---|---|---|
| UX-00 | Cover | PREGÓN · «Escríbele al distrito.» · R-IN · S-SIDE · H-CHAT · F-KAN · L1 admin pública · highlighter · 2026-08-20 |
| UX-01 | Stakeholders | VECINO (Rosa), OPERADOR (Nuria), distrito, 112 fuera de alcance |
| UX-02 | Personas | Rosa Méndez 63 · Nuria Delgado 41 · Andrés Peña 38 (vecino digital) |
| UX-03 | JTBD | Job «decir que la farola no enciende sin adivinar el formulario» |
| UX-04 | Stories | Must: chat, envío, acuse, login, kanban, ficha, estados |
| UX-05 | Journey | Lee bando → escribe parte → código → (Nuria) mesa → En curso → Cerrado |
| UX-06 | Blueprint | Frontstage chat · backstage brigada · Nest/Neon/JWT |
| UX-07 | Site map | Público / chat · /ok/:code · /login · /mesa kanban |
| UX-08 | Flujos | F-CHAT público; F-KAN OPERADOR |
| UX-09 | Datos | User OPERADOR · Report · Bando; JWT en mesa |
| UX-10 | Métricas | North star % partes que Nuria mueve a En curso en 24 h |
| UX-11 | Research | Comprobado / supuesto / hipótesis / decisión |
UI producto (prefijo UI- · mínimo 10)
| ID | Nombre | Flujo |
|---|---|---|
| UI-00 | Tokens | DS highlighter: fog, ink, mark, card, graphite, seal |
| UI-01 | Chat home | H-CHAT 1440: bandos + hilo + composer. Lead + 3 datos |
| UI-02 | Chat mobile | 390: mismo hilo |
| UI-03 | Composer | Tipos farola/cubo/banco/acera |
| UI-04 | Success | Código. Enviado ≠ resuelto |
| UI-05 | Login | JWT OPERADOR |
| UI-06 | Kanban | Recibido / En curso / Cerrado |
| UI-07 | Ficha | Parte + foto + mover columna |
| UI-08 | Empty | Sin partes / chat vacío |
| UI-09 | Error | Red |
| UI-10 | Loading | Skeletons |
| UI-11 | Mesa mobile | Lista por columna |
Mapeo Paper → Angular
| Artboard | Ruta | Componente |
|---|---|---|
| UI-01 Chat | / | ChatPage |
| UI-04 Success | /ok/:code | OkPage |
| UI-05 Login | /login | LoginPage |
| UI-06 Kanban | /mesa | MesaPage |
| UI-07 Ficha | /mesa/:code | PartePage |
01-project-definition.md
01 — Definición de proyecto — PREGÓN
1. Identidad
| Campo | Valor |
|---|---|
| Nombre | PREGÓN |
| Significado | Pregón es el pregonero: el aviso público del distrito, no el trámite. Ni la sede electrónica ni el 112. Se entra al hilo; se sale con un código. |
| Eslogan | «Escríbele al distrito.» |
| Una frase | Chat-first de incidencias vecinas + mesa OPERADOR JWT (kanban Recibido / En curso / Cerrado). |
| Sector | Administración pública local (distrito, no ministerio ni 112) |
| Tipo | Web L1 — chat público + acuse + login + kanban + ficha — Nivel 1, producto usable compacto |
| Plataforma | Web responsive (chat vecino ~390 · mesa desktop con rail) |
| Mercado demo | España · Madrid · distrito Usera / Villaverde (una mesa, no el Ayuntamiento entero) |
| Idioma | es-ES |
| Fecha caso | 2026-08-20 |
| Terna | R-IN · S-SIDE · H-CHAT · F-KAN |
[COMPROBADO] Nombre, terna, nivel, sector, ground y paleta salen de docs/00-day-brief.md.
2. Problema
Principal (hipótesis de diseño)
[HIPÓTESIS] El vecino de un distrito del sur de Madrid no necesita otro formulario de sede: necesita decirle al distrito que la farola no enciende sin adivinar el procedimiento. Hoy eso vive en WhatsApp al vecino, llamada al 010 que no es el 112, y un acuse que se lee como «ya está arreglado». La operadora vive en una bandeja que no distingue recibido de cerrado. [SUPUESTO] Un distrito atiende 8–20 partes/día; no opera un SIA de consejería ni un GIS corporativo.
Secundarios
| Problema | Quién lo sufre | Efecto |
|---|---|---|
| «No sé qué formulario es» | VECINA (Rosa) | Abandona; escribe a un grupo de WhatsApp; no llega a Nuria |
| Enviado se lee como resuelto | VECINA + OPERADOR | «Ya puse el parte» cuando la farola sigue apagada |
| El chat se lee como 112 | VECINA + distrito | Urgencia vital en un canal de farolas |
| El parte se lee como expediente SIA | OPERADOR + distrito | Esperan número de procedimiento, plazos, sede |
| La mesa se lee como cita | VECINA | Esperan «te llamamos a las 10» |
| La dueña de la bandeja pide GIS el día 1 | Distrito | El L1 se vuelve geoportal |
Supuestos (no investigación primaria propia)
- S1: Un distrito atiende 8–20 partes/día; la demo tiene 7 partes en tres columnas. [SUPUESTO]
- S2: El vecino escribe desde el móvil en la acera o el banco de la plaza; no crea cuenta. [SUPUESTO]
- S3: Un OPERADOR (Nuria) mueve columnas; no hay sede electrónica ni rol de brigada autenticado. [SUPUESTO]
- S4: Tipos: farola, cubo, banco, acera, otro. Sin GIS. [SUPUESTO]
Hipótesis de producto
| ID | Hipótesis | Señal de validación (futura) |
|---|---|---|
| H1 | Un chat reduce «no sé qué formulario es» | % de visitas a / que terminan en POST con código |
| H2 | Separar enviado de resuelto evita la expectativa 112 / «ya está» | ↓ «pensé que ya estaba arreglado»; copy visible en acuse |
| H3 | Kanban de 3 columnas basta; el vecino no necesita login | 0 cuentas de vecino; Nuria mueve a En curso en 24 h |
No se afirman estadísticas de juntas de distrito inventadas. Todo lo anterior es razonamiento de diseño etiquetado.
3. Propuesta de valor
| Para | Valor |
|---|---|
| VECINA (Rosa Méndez) | Escribe calle, tipo y texto desde el banco de la plaza, sin cuenta, y se lleva un código. Nadie le pide DNI ni cita. |
| OPERADOR (Nuria Delgado) | Ve Recibido / En curso / Cerrado. Abre el parte. Mueve la columna. No tramita un expediente SIA. |
| VECINO (Andrés Peña) | Mismo chat, más rápido. El código le basta; no pide un panel de seguimiento. |
| Distrito Usera / Villaverde | Un pregón mínimo: bandos + partes + mesa, sin 112 y sin sede. |
No es PREGÓN
| Excluido | Por qué |
|---|---|
| Expediente SIA / número de procedimiento | Mensaje ≠ expediente |
| «Ya está resuelto» al enviar | Enviado ≠ resuelto |
| Cita / slot / «te llamamos a las 10» | PREGÓN no cita |
| 112 / urgencia vital / incendio / agresión | PREGÓN no es 112 |
| Sede electrónica / Cl@ve / certificado | S3: el vecino no se identifica |
| GIS / mapa de farolas | S4: calle en texto |
| Cuaderno de fórmulas / sillas de hoy | Eso fue MATIZ (H-BENTO / F-EDIT) |
| Wizard de criterios + comparador de pisos | Eso fue ZAGUÁN (H-WIZARD / F-CMP) |
| Lookbook / masthead editorial | Eso fue MARGEN (H-GALLERY / F-STORY) |
| Hero 2-col + 3 cards de «tu ayuntamiento» | Anti-patrón AP-12; contradice H-CHAT |
| App de incidencias con 40 estados y GIS | L1 compacto, no geoportal |
4. Objetivos
Negocio / caso de estudio
- Demostrar vertical slice L1 admin pública local con Bando + Report + JWT OPERADOR.
- Portfolio coherente: Paper (12 UX + 12 UI) + docs + app runnable.
- Terna R-IN · S-SIDE · H-CHAT · F-KAN frente a MATIZ (bento/editor), ZAGUÁN (wizard/comparador) y MARGEN (galería/masthead).
Usuario
| Rol | Objetivo medible en demo |
|---|---|
| VECINA | Leer un bando, escribir un parte y ver el código en < 60 s, sin cuenta |
| OPERADOR | Entrar a mesa, ver 3/2/2, abrir PG-2401 y pasarlo a En curso en < 45 s |
| VECINO digital | Mismo flujo; no encuentra login de vecino ni tablero |
No objetivos v1 (explícitos)
- Sede electrónica, Cl@ve, certificado, DNI
- 112, policía, bomberos, urgencia vital
- Cita previa, slot, «te llamamos»
- GIS, mapa, coordenadas, inventario de farolas
- Cuenta de vecino, seguimiento con login, push
- Multi-operador, turnos, brigada autenticada
- Chat entre vecinos (foro)
- App nativa ni notificaciones push
- Rol JUNTA / JEFE autenticado
5. Roles y permisos (resumen)
| Acción | Anónimo (vecino) | OPERADOR | JUNTA / 112 |
|---|---|---|---|
| Ver chat / bandos | Sí | Sí | No hay cuenta |
| POST parte | Sí | Sí (no es el job) | No |
Ver acuse /ok/:code | Sí (código en URL) | Sí | — |
| Ver mesa / ficha | No (401 / redirect) | Sí | No |
GET /api/reports | 401 | Sí | — |
| PATCH status | 401 | Sí | — |
| Abrir expediente SIA | No (no existe) | No | Fuera |
| Llamar al 112 | Copy: fuera | Copy: fuera | El 112 |
[DECISIÓN] Un solo rol autenticado: OPERADOR. El vecino no es un usuario del sistema: es autora/autor de un report. La junta no es un rol de sistema el día 1.
6. Métricas (modelo, no instrumentadas en v1 salvo base)
| Tipo | Métrica | Definición |
|---|---|---|
| North Star | % de partes que Nuria mueve a En curso en 24 h | Oficio de mesa, no pageviews |
| Activación vecina | 1.er POST con code | H1 |
| Integridad de expectativa | Reclamaciones «ya está resuelto» / «es el 112» / «¿dónde está mi cita?» | H2 + microcopy |
| Integridad de auth | GET /api/reports sin token → 401; POST /api/reports sin token → 201 | Chat público + mesa cerrada |
| Salud | Error rate API reports / bandos | 4xx/5xx |
7. Alcance funcional v1 (L1)
| Módulo | Incluido |
|---|---|
| Chat | GET /api/bandos · hilo + compositor (calle, tipo, texto, nombre) |
| Parte público | POST /api/reports · { street, kind, body, authorName } → PG-XXXX RECIBIDO |
| Acuse | /ok/:code · «enviado ≠ resuelto» |
| Auth | POST /api/auth/login → JWT OPERADOR |
| Mesa | GET /api/reports JWT · kanban Recibido / En curso / Cerrado |
| Ficha | GET /api/reports/:code JWT · PATCH { status } |
| Estados UI | Empty (0 bandos / 0 partes), loading skeleton, error de red |
| Mobile | Chat ~390px · mesa lista por columna · targets ≥44px |
| Seed | 1 OPERADOR, 3 bandos, 7 partes |
| Salud | GET /api/health → { ok: true, service: "pregon-api" } |
8. Criterios de aceptación de producto
/es el chat público. No pide cuenta. No es un hero 2-col + 3 cards.- Una vecina puede leer 3 bandos en el fold, escribir un parte (calle + tipo + texto + nombre) y obtener un código
PG-XXXX. /ok/:codedice explícitamente que enviado no es resuelto y que PREGÓN no es 112.- La UI dice explícitamente que mensaje no es expediente, que PREGÓN no cita y que PREGÓN no es 112.
- Sin token, GET
/api/reports, GET/api/reports/:codey PATCH responden 401. POST/api/reportsy GET/api/bandosson públicos. - Un POST de parte crea
status=RECIBIDO. Enviar no marca CERRADO. - Una OPERADOR inicia sesión y ve el kanban con 3 RECIBIDO, 2 CURSO, 2 CERRADO en el seed.
- Una OPERADOR abre un parte y lo mueve de columna (RECIBIDO → CURSO → CERRADO).
- Fotos reales en las partes seed (assets
partes/*). - Tipos visibles: farola · cubo · banco · acera · otro.
- Rail S-SIDE de estados en mesa desktop. Chat público sin top-nav de marketing.
- Rutas solo las del doc 03. No existen
/cita,/112,/sede,/expediente,/mapa. GET /api/healthdevuelveservice: "pregon-api"— nomatiz-api.
9. Stack y artefactos
| Capa | Detalle |
|---|---|
| Frontend | Angular + Tailwind · puerto 4200 |
| Backend | NestJS · puerto 3020 |
| DB | Neon PostgreSQL · project empty-surf-15969880 |
| Auth | JWT (OPERADOR) |
| Diseño | Paper 01M0EZQ31KF04PAQ5RMCV7DQ2F |
| Repo app | /Users/cristian/orca/pregon-app/ · GitHub Criscode2022/pregon-app |
[COMPROBADO] Puerto, Neon, Paper file ID, repo y path de app constan en el encargo.
10. Riesgos y mitigaciones
| Riesgo | Impacto | Mitigación v1 |
|---|---|---|
| Expectativa de expediente | «¿Cuál es mi número SIA?» | Copy «Mensaje ≠ expediente» |
| Expectativa de resuelto | «Ya lo envié, ¿por qué sigue la farola?» | Copy «Enviado ≠ resuelto» en acuse |
| Expectativa de 112 | Urgencia vital en el chat | Copy «PREGÓN no es 112»; bando seed bnd_3 |
| Expectativa de cita | «Te llamamos a las 10» | Copy «PREGÓN no cita»; cero slot |
| GIS el día 1 | Alcance L3 disfrazado | Calle en texto; S4 |
| Cuenta de vecino | Rompe S2 / H3 | Sin signup; nombre libre en el parte |
| Home percibida como landing municipal | Pérdida de H-CHAT / AP-12 | El hilo es la home; no hero 2-col |
| Confundir con MATIZ (cuaderno de oficio) | Expectativa de login-first | Chat público; no Hoy bento |
| Confundir con ZAGUÁN (wizard) | Expectativa de pasos 1—2—3 | Un compositor, no wizard |
Leftover clients / formulas / hoy en repo | App enseña otro producto | Contrato de esta suite gana; I01 de build |
| Enumeración de partes por código | PII de calle en público | GET :code JWT; /ok/:code no hidrata ficha |
| 0 bandos | Chat vacío sin oficio | Empty «Aún no hay bandos del distrito.» |
11. Glosario
| Término | Definición en PREGÓN |
|---|---|
| Pregón | Aviso público del distrito; el producto, no un trámite |
| Bando | Mensaje anclado del distrito en el chat (bandos) |
| Parte / report | Incidencia vecina (reports); no es un expediente |
| Mensaje | Lo que escribe Rosa en el compositor; no abre procedimiento SIA |
| Expediente | Procedimiento administrativo; fuera. Mensaje ≠ expediente |
| Enviado | POST 201 + código; no significa resuelto |
| Resuelto | Trabajo de brigada en la calle; fuera de la app hasta CERRADO de mesa |
| Código | PG-XXXX; acuse, no número SIA |
| Recibido | Columna 1 · RECIBIDO · acaba de entrar |
| En curso | Columna 2 · CURSO · Nuria lo está atendiendo |
| Cerrado | Columna 3 · CERRADO · la mesa lo da por hecho; no afirma la farola encendida |
| Tipo / kind | farola | cubo | banco | acera | otro |
| Mesa | Superficie F-KAN de la OPERADOR |
| Chat | Superficie H-CHAT pública |
| OPERADOR | Usuario autenticado que opera la mesa |
| VECINA / VECINO | Autora/autor del parte; no tiene fila en users |
| Fog | Superficie de ground #F1F3F6 |
| Ficha / card | Superficie de card #FFFFFF |
| Mark | Rotulador fluorescente #C8F000 |
| Lacre | Acento de sello / urgente #C1121F, poco |
| Rail | Shell S-SIDE: estados de la mesa |
| 112 | Emergencia vital; fuera |
12. Decisiones de diseño (cierre de brief)
| ID | Decisión | Alternativa descartada |
|---|---|---|
| D1 | Home = chat (H-CHAT) | Hero 2-col + 3 cards (AP-12) · bento (MATIZ) · wizard (ZAGUÁN) · galería (MARGEN) |
| D2 | Shell rail de estados en mesa (S-SIDE) | Bottom tabs · masthead · wizard chrome · dock |
| D3 | Registro innovador highlighter (R-IN) | Chalky / pizarra (primer instinto; eso fue PIZARRA) · chapel MATIZ |
| D4 | Flujo F-KAN (operadora mueve columnas) | F-EDIT, F-CMP, F-STORY, F-LEAD, F-BOOK |
| D5 | Mood highlighter | Chalky (vetado como primer instinto), chapel MATIZ, rusted ZAGUÁN |
| D6 | Un rol autenticado OPERADOR | Multi-rol junta / brigada / vecino / 112 |
| D7 | Estados de parte RECIBIDO | CURSO | CERRADO | 12 estados SIA; RESUELTO como eufemismo de enviado |
| D8 | Chat vecino sin cuenta | Signup, Cl@ve, seguimiento con login |
| D9 | GET :code solo JWT | Tracking público enumerabile |
| D10 | POST público { street, kind, body, authorName } | Formulario de 12 campos + adjuntos + DNI |
| D11 | Ground G-FOG #F1F3F6 | G-CHAPEL, G-CONCRETE, G-GALLERY, G-MOSS, G-CHROME |
| D12 | Success de acuse en /ok/:code | Toast único sin pantalla |
| D13 | Kind = farola | cubo | banco | acera | otro | Taxonomía de 40 tipos + GIS |
| D14 | Hilo público = bandos, no feed de partes ajenos | Foro vecinal (rompe privacidad de calle) |
| D15 | Chat público + mesa JWT (no auth-first total) | Todo cerrado (rompería S2) · todo abierto (rompería mesa) |
13. Relación con el día anterior
| Case | Terna | Por qué PREGÓN no lo copia |
|---|---|---|
| MATIZ (N−1) | S-BOTTOM / H-BENTO / F-EDIT | No belleza, no PWA de oficio, no editor de gramos, no auth-first total |
| ZAGUÁN (N−2) | S-WIZ / H-WIZARD / F-CMP | No inmobiliaria, no wizard, no comparador |
| MARGEN (N−3) | S-TOP / H-GALLERY / F-STORY | No editorial, no lookbook, no masthead |
| PIZARRA | H-SEARCH / catálogo | Chat ≠ catálogo search; incidencia ≠ plaza de taller |
[COMPROBADO] El brief fija terna R-IN · S-SIDE · H-CHAT · F-KAN. 0 códigos iguales a N−1 (MATIZ). ≥2 distintos vs N−2 (ZAGUÁN) y N−3 (MARGEN). Ventana 4 días: R-HY + R-IN + R-CL + R-IN. Ground G-FOG #F1F3F6, no G-CHAPEL.
02-ux-research-strategy.md
02 — Estrategia de investigación UX — PREGÓN
Importante: este documento contiene artefactos de diseño y razonamiento secundario.
No hay entrevistas de campo primarias ni estadísticas inventadas presentadas como dato medido.
Etiquetas: [COMPROBADO] en producto, [SUPUESTO], [HIPÓTESIS], [DECISIÓN].
1. Objetivos de investigación (del caso)
| Objetivo | Método en este caso | Salida |
|---|---|---|
| Entender actores del distrito | Modelado de stakeholders + personas | §3–4 |
| Definir job de «decir que la farola no enciende» y de mover columnas | JTBD + stories Must | §5 |
| Mapear fricción sede / WhatsApp / 010 → chat | Journey + service blueprint | §6–7 |
| Traducir a requisitos L1 chat + kanban | Matriz hallazgo → requisito → feature | §8 |
2. Fuentes y límites
Fuentes admisibles (secundarias / operativas)
- Conocimiento general de incidencias urbanas de barrio (farola, cubo, banco, acera; distinción 010 vs 112; acuse vs arreglo).
- Analogía operativa con chat público + mesa JWT de la serie daily.
- Restricciones ALS-2: no sede electrónica, no 112, no cita, no GIS, no cuaderno de oficio, no wizard inmobiliario.
Límites éticos de verdad
| Prohibido | Permitido |
|---|---|
| «El 68% de vecinas de Usera no encuentra el formulario de farola» sin fuente | «[SUPUESTO] Rosa escribe desde el banco de la plaza, con poco hábito digital» |
| Citas de entrevistas ficticias como campo real | Quotes de persona etiquetadas como constructo de diseño |
| Volumen medido «14 partes/día en Villaverde» como KPI de mercado | Partes de seed de producto, etiquetadas como demo |
| Horario oficial de una junta concreta Usera-Villaverde | Mesa de seed; Nuria y Rosa son personas de diseño |
| «Rosa existe y nos dijo…» | Rosa Méndez es persona de diseño |
3. Stakeholders
| Stakeholder | Influencia | Interés | Necesidad principal |
|---|---|---|---|
| VECINA (Rosa) | Alta en el parte | Muy alta | Decir lo de la farola sin cuenta ni formulario |
| OPERADOR (Nuria) | Alta | Muy alta | Ver lo recibido, mover a curso, cerrar |
| VECINO digital (Andrés) | Media | Alta | Mismo chat, más rápido; no pide panel |
| Distrito / junta | Alta formal, baja en producto día 1 | Alta | Que lleguen partes; que no se confunda con 112 |
| Brigada de alumbrado / limpieza | Media (fuera) | Alta | Recibe el trabajo fuera de PREGÓN |
| 112 / SAMUR / Policía | Alta en emergencias | — | Fuera de alcance; no clonar el canal |
| 010 / Linea Madrid / sede | Competencia / canal paralelo | — | No clonar el expediente; sí sustituir «no sé qué formulario es» |
| Vecindario WhatsApp | Canal paralelo | — | No es el backoffice |
Mapa de poder (resumen)
- Decisor de adopción: la junta (quién publica el pregón) y, en uso diario, Nuria (quien mueve columnas).
- Usuario frecuente de escritura: VECINA / VECINO (chat, sin cuenta).
- Usuario frecuente de triaje: OPERADOR (mesa + ficha).
- Riesgo de rechazo: si el acuse parece resuelto; si el chat parece 112; si hay que crear cuenta; si la mesa parece un GIS.
4. Personas
P1 — Rosa Méndez · VECINA
| Campo | Detalle |
|---|---|
| Edad / contexto | 63 años; vecina de Usera; poco hábito digital; escribe desde el banco de la plaza |
| Digital | Móvil con letra grande; no quiere «otra cuenta del ayuntamiento»; no usa Cl@ve |
| Goals | Decir que la farola no enciende; llevarse un código; no adivinar el formulario |
| Pains | La sede pide certificado; el 010 no es el banco de la plaza; le da miedo pulsar mal |
| Quote de diseño | «Solo quiero decir lo de la farola. No me hagas una cuenta.» |
Escenario: Abre / en el móvil, lee el bando del agua, elige farola, escribe plaza Elíptica, se llama Rosa, envía, lee «Enviado no es resuelto» y el código PG-XXXX.
[COMPROBADO] Nombre, edad y gesto (banco de la plaza) salen del encargo. Es constructo de diseño, no entrevistada. [DECISIÓN]
P2 — Nuria Delgado · OPERADOR
| Campo | Detalle |
|---|---|
| Edad / contexto | 41 años; mesa del distrito Usera / Villaverde; turno de incidencias de calle |
| Digital | Fluida en desktop; el móvil es secundario para la mesa |
| Goals | Ver lo nuevo, abrir el parte, pasarlo a En curso, cerrar cuando la brigada avisa (fuera) |
| Pains | Bandeja de correo mezclada; el vecino llama «¿ya está?»; el GIS de la casa no le cabe el día 1 |
| Quote de diseño | «Tres columnas me bastan. No me des un expediente.» |
| Email demo | nuria@pregon.ayto / password123 |
Escenario: Login → mesa 3/2/2 → abre PG-2401 (Rosa, farola, plaza Elíptica) → mueve a En curso → no encuentra «citar» ni «112».
[COMPROBADO] Credencial demo sale del encargo. Nombre, edad y mesa son constructo de diseño. [DECISIÓN]
P3 — Andrés Peña · VECINO digital (secundario)
| Campo | Detalle |
|---|---|
| Edad / contexto | 38 años; Villaverde; usa el móvil todo el día |
| Digital | Alta; escribe rápido; no necesita onboarding |
| Goals | Dejar el cubo apuntado en 20 s; guardar el código en el rollo de fotos |
| Pains | Formularios de 12 campos; captchas; «regístrate para hacer seguimiento» |
| Quote de diseño | «El código me vale. No me hagas un panel.» |
Escenario: Mismo chat. Tipo cubo, Marcelo Usera 42, envía, captura /ok/PG-XXXX. No entra a /mesa.
[DECISIÓN] Andrés no es rol de sistema. Es la misma superficie que Rosa, con menos fricción percibida.
Autoras/autores de seed (no personas primarias extra)
| Nombre | Rol en seed | Partes |
|---|---|---|
| Rosa Méndez | P1 | PG-2401, PG-2403, PG-2405, PG-2407 |
| Andrés Peña | P3 | PG-2402, PG-2404, PG-2406 |
[COMPROBADO] Nombres salen del encargo. Los textos de parte son catálogo demo.
Anti-personas
| Quién | Por qué no es target v1 |
|---|---|
| Jefa de sede electrónica | S3: no hay Cl@ve ni expediente |
| Vecina que quiere «cita con el concejal» | PREGÓN no cita |
| Quién llama por un infarto | PREGÓN no es 112 |
| Community manager de smart city | S1: 8–20 partes/día; no es geoportal |
| Colorista de MATIZ que espera Hoy bento | Otro job, otro día |
| Buscador de ZAGUÁN que pregunta «tres fincas» | Otro job, otro día |
| Operador que quiere 12 estados SIA | H3: 3 columnas |
5. JTBD y user stories
Job principal (VECINA)
Cuando la farola del banco no enciende y estoy sentada debajo,
quiero escribirle al distrito en el mismo hilo del bando,
para no adivinar el formulario ni crear una cuenta.
Job principal (OPERADOR)
Cuando abro la mesa por la mañana,
quiero ver lo recibido, lo que está en curso y lo cerrado,
para no cazar partes en una bandeja de correo.
Job secundario (VECINO digital)
Cuando veo el cubo rebosando de camino al metro,
quiero dejar calle, tipo y dos líneas,
para seguir andando con un código en el bolsillo.
Jobs secundarios
| Job | Rol |
|---|---|
| Distinguir enviado de resuelto | VECINA / OPERADOR |
| Entender que no es el 112 | VECINA / distrito |
| Entender que no es un expediente | OPERADOR / distrito |
| No crear cuenta | VECINA |
| Mover un parte de columna | OPERADOR |
| No ver el tablero desde la calle | VECINA (H3) |
Stories Must (v1)
| ID | Story | AC |
|---|---|---|
| US1 | Como vecina, quiero leer los bandos del distrito sin cuenta | GET /api/bandos · 3 en el fold |
| US2 | Como vecina, quiero escribir calle, tipo y texto | Composer UI-03 |
| US3 | Como vecina, quiero un código al enviar | POST → PG-XXXX · /ok/:code |
| US4 | Como vecina, quiero que el acuse no diga «resuelto» | Copy enviado ≠ resuelto |
| US5 | Como vecina, quiero que me digan que no es el 112 | Copy + bando bnd_3 |
| US6 | Como operadora, quiero entrar con email/password | JWT + redirect /mesa |
| US7 | Como operadora, quiero ver Recibido / En curso / Cerrado | GET /api/reports agrupado |
| US8 | Como operadora, quiero abrir un parte | GET /api/reports/:code |
| US9 | Como operadora, quiero mover la columna | PATCH { status } |
| US10 | Como cualquiera sin token, no quiero ver la mesa | 401 / redirect login |
| US11 | Como vecina u operadora, quiero empty / error / loading | UI-08, UI-09, UI-10 |
| US12 | Como vecina en el banco, quiero targets ≥44px | Chat 390 |
MoSCoW (v1 L1)
| Prioridad | Ítems |
|---|---|
| Must | Chat bandos, compositor, acuse, login, kanban, ficha, estados, empty/error/loading, mesa mobile |
| Should | Rail S-SIDE con contadores; chips de tipo; pie «no es 112»; logout en mesa |
| Could | GET /api/me; foto en POST; filtro por tipo en mesa; ?view=flat |
| Won’t | Cuenta vecina, GIS, 112, cita, expediente SIA, foro vecinal, brigada JWT |
6. Journey (VECINA · farola de la plaza + OPERADOR)
| Fase | Actor | Acción | Touchpoint | Emoción [HIPÓTESIS] |
|---|---|---|---|---|
| 1 Leer el pregón | Rosa | Abre /; lee 3 bandos | Chat H-CHAT | Oficio, «esto no es la sede» |
| 2 Escribir | Rosa | Calle, farola, texto, su nombre | Composer | Concentración (banco, sol, letra grande) |
| 3 Enviado ≠ resuelto | Rosa | Ve el código; lee el disclaimer | /ok/:code | Control (H2 + microcopy) |
| 4 No es 112 | Rosa | No llama al chat por un infarto | Copy + bnd_3 | Límite claro |
| 5 Entrar a mesa | Nuria | Login JWT; aterriza en kanban | /login → /mesa | Oficio, «tres columnas» |
| 6 Triaje | Nuria | Abre PG-2401; pasa a En curso | /mesa/PG-2401 | Control (H3) |
| 7 Cerrar (más tarde) | Nuria | La brigada avisa fuera; Nuria cierra | Mesa | Cierre de mesa, no milagro de farola |
| 8 No citar | Nuria | No hay slot; Rosa no espera llamada a las 10 | Ausencia de UI | Integridad |
Momentos de verdad
- 3 bandos + compositor en el primer viewport — si hay que «descubrir la marca», se rompe H-CHAT / AP-21. [HIPÓTESIS]
- Sin cuenta — si aparece «regístrate», S2 / H3 fallan.
- Success dice que enviado no es resuelto — evita la mentira de la farola apagada.
- Cero CTA de 112 como botón de envío y cero cita — si aparece «Llamar 112» como primary o «Pedir cita», el microcopy falla. (El aviso de que no es 112 sí debe verse.)
- Kanban de 3 columnas, rail a la izquierda — S-SIDE / F-KAN. [HIPÓTESIS]
7. Service blueprint (resumen)
| Capa | Elementos |
|---|---|
| Frontstage VECINA | Chat, bandos, composer, acuse. Sin mesa |
| Frontstage OPERADOR | Login, kanban, ficha, cambio de columna |
| Backstage | Brigada de alumbrado / limpieza (fuera), 112 (fuera), 010 (fuera), WhatsApp de barrio |
| Sistemas | Nest API :3020, Neon empty-surf-15969880, JWT, Angular |
| Soportes | Seed 1 OPERADOR + 3 bandos + 7 partes + fotos, Paper, docs |
| Fallos | 0 bandos; 401 en mesa; 404 código; red caída → UI error; validación de parte |
Fallos de servicio y respuesta de diseño
| Fallo | Evidencia de UI | Recuperación |
|---|---|---|
| 0 bandos | UI-08 Empty chat | «Aún no hay bandos del distrito.» + compositor igual visible |
| 0 partes en una columna | UI-08 Empty columna | «No hay partes en Recibido.» (sin CTA de cita) |
| API caída | UI-09 Error | Reintentar |
| Latencia chat | Loading (doc 16) | Skeleton hilo (3 bloques bando + barra composer) |
| Form inválido | Inline field | No navegar a /ok |
| Código inexistente en acuse | Acuse genérico | Muestra el código de la URL + copy; no inventa el parte |
| Código inexistente en mesa | 404 | «Ese parte no está.» + volver a Mesa |
Sin token en /mesa | Redirect | /login |
| POST spam | 429 Should | Rate limit L+1 |
8. Matriz hallazgo → requisito → feature
| Hallazgo | Tipo | Requisito | Feature v1 |
|---|---|---|---|
| Escribe desde el banco, poco digital | [SUPUESTO] S2 | Chat 390, targets ≥44, sin cuenta | H-CHAT · UI-02 |
| No sabe qué formulario es | [HIPÓTESIS] H1 | Un compositor, no 12 campos | POST reports |
| Enviado se lee como resuelto | [HIPÓTESIS] H2 | Copy irrenunciable | UI-04 Success |
| El canal se lee como 112 | [DECISIÓN] | Copy + bando | UI-01 + bnd_3 |
| El parte se lee como expediente | [DECISIÓN] | Copy irrenunciable | Chat + mesa |
| Cita el día 1 | [DECISIÓN] | Cero slot | «PREGÓN no cita» |
| Operadora no quiere GIS | [SUPUESTO] S4 | Calle en texto | Composer + ficha |
| Vecino no necesita login | [HIPÓTESIS] H3 | Público POST + JWT solo mesa | Guards |
| L1 compacto, no SIA | [DECISIÓN] | 3 columnas, 7 partes | F-KAN |
9. Preguntas abiertas (no bloquean v1)
| ID | Pregunta | Cómo se resolvería después |
|---|---|---|
| Q1 | ¿La vecina recarga /ok/:code y pierde calle/tipo? | [DECISIÓN v1] el acuse vive del POST + URL. Could: acuse público mínimo { code, status } |
| Q2 | ¿Nuria escribe una nota a la brigada? | Fuera v1. Could L+1: nota interna, no chat al vecino |
| Q3 | ¿Foto en el POST? | S4 no lo exige. Seed tiene cover. L+1 allowlist |
| Q4 | ¿Usera y Villaverde son una junta real unificada? | No. Atrezzo de mercado. Son dos distritos; la demo es una mesa. [DECISIÓN] |
| Q5 | ¿Las fotos de farola/cubo son incidencias reales? | Assets de case; no se presentan como partes oficiales. [SUPUESTO de catálogo] |
| Q6 | ¿El correo @pregon.ayto existe? | No. Credencial demo. |
10. Plan de research futuro (si hubiera campo real)
| Método | Muestra orientativa | Pregunta |
|---|---|---|
| Test de usabilidad chat 390 | 5–6 vecinas 55–75 | ¿Envían un parte en < 60 s sin cuenta? |
| Shadowing de mesa | 1 operadora de distrito | ¿Tres columnas sustituyen la bandeja? |
| Revisión de expectativa 112 | Clics vs «¿es una emergencia?» | Validar H2 |
| Recorrido con VoiceOver + letra grande | 5 sesiones | ¿Los targets de 44px bastan en el banco? |
Estos métodos no se han ejecutado. No se reportan hallazgos como si lo hubieran sido.
11. Síntesis
PREGÓN se diseña como pregón que se escribe y se mueve de columna, no como sede, no como 112 y no como expediente.
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 — PREGÓN
1. Principios de IA
| Principio | Aplicación |
|---|---|
| Chat first | La home es el hilo. No hay landing de marca por delante |
| Público / mesa | El vecino escribe sin cuenta; Nuria entra con JWT |
| Hilo → acuse | Profundidad 1 desde el compositor hasta el código |
| Mesa → ficha → columna | Profundidad 1 desde el kanban hasta el PATCH |
| Lenguaje de dominio | Parte, bando, código, Recibido / En curso / Cerrado — no «ticket», «lead», «expediente», «silla» |
| Mensaje no es expediente | El parte no tiene número SIA ni plazos |
| Enviado se mira | Success no tiene «Resuelto» ni «Citar» |
| Rail de estados en mesa | S-SIDE: Recibido · En curso · Cerrado; el chat no lleva top-nav de marketing |
2. Sitemap
/ Chat público (bandos + compositor) H-CHAT
/ok/:code Acuse post-envío público
/login Login JWT OPERADOR público
/mesa Kanban Recibido / En curso / Cerrado F-KAN · auth
/mesa/:code Ficha de parte + mover columna auth
/** → redirect / (si no hay token) o /mesa
Árbol por audiencia
| Audiencia | Nodos relevantes |
|---|---|
| VECINA / VECINO | Chat → Ok. No ven mesa |
| OPERADOR | Login → Mesa → Ficha · (puede leer el chat) |
| Junta / 112 | Ninguno como rol de sistema |
[COMPROBADO] Cinco rutas del encargo L1. No se inventan /cita, /112, /sede, /expediente, /mapa, /hoy, /fichas.
3. Navegación
Chat público (sin top-nav de marketing)
| Elemento | Destino | Notas |
|---|---|---|
| Wordmark PREGÓN | / | No es hero; es sello del hilo |
| Pie / link Mesa | /login o /mesa si hay token | Secundario, grafito, no CTA de marca |
| Composer | POST → /ok/:code | Anclado al fondo en 390 |
[DECISIÓN] El chat no tiene tabs, no tiene hamburger, no tiene «Servicios / Sede / Cita». Un link «Mesa» discreto basta para Nuria.
Rail de mesa (S-SIDE)
| Slot | Destino | Visibilidad | Notas |
|---|---|---|---|
| Recibido | /mesa foco columna 1 | Auth | Contador seed 3 |
| En curso | /mesa foco columna 2 | Auth | Contador seed 2 |
| Cerrado | /mesa foco columna 3 | Auth | Contador seed 2 |
| Salir | limpia token → /login | Auth | Al pie del rail |
[DECISIÓN] El rail es la única navegación primaria autenticada. Encaja L1 mesa. No hay bottom tabs (MATIZ), no masthead (MARGEN), no chrome de pasos (ZAGUÁN).
Desktop ≥960px: rail izquierdo 220–260px + tablero 3 columnas.
Mobile ~390px: el rail se convierte en segmentos Recibido / En curso / Cerrado + lista de esa columna (UI-11). [DECISIÓN] En 390 no hay hamburger de marketing ni tres columnas aplastadas.
Header contextual (mesa; no es nav primaria del chat)
| Superficie | Izquierda | Centro | Derecha |
|---|---|---|---|
Chat / | Wordmark | — | Mesa (ghost) |
| Ok | Wordmark | — | Escribir otro |
| Login | Wordmark | — | — |
| Mesa | Wordmark en rail | — | Nuria · Salir |
| Ficha | ← Mesa | Código | — |
[DECISIÓN] El wordmark en autenticado lleva a /mesa (no al chat), para no mezclar oficios. El wordmark en público lleva a /.
Login (público; solo oficio)
| Elemento | Destino | Notas |
|---|---|---|
| Email / password / Entrar | POST login → /mesa | JWT |
| Error | no navega | Inline |
| Pie | no «crear cuenta de vecino» | No hay signup |
| Volver al chat | / | Ghost |
[DECISIÓN] No hay /registro. No hay «Soy vecina». No hay Cl@ve.
4. Inventario de contenido
| Pantalla | Contenidos |
|---|---|
Chat / | Wordmark, eslogan, 3 bandos, hilo, compositor (nombre, calle, tipo, texto), disclaimers, empty, loading, error |
| Composer | Chips farola/cubo/banco/acera/otro + campos + Enviar |
Success /ok/:code | Código, «enviado ≠ resuelto», «no es 112», CTA escribir otro |
Login /login | Wordmark, eslogan, email, password, submit, error, volver al chat |
Mesa /mesa | Rail S-SIDE, 3 columnas, cards de parte (código, tipo, calle, autora), empty, loading, error |
Ficha /mesa/:code | Código, foto, calle, tipo, cuerpo, autora, fecha, chips de columna, disclaimers |
| Empty | Chat sin bandos · columna sin partes |
| Error | Red |
| Loading | Skeletons de hilo / columnas |
| Mesa mobile | Segmentos + lista |
5. Taxonomía
Role
| Status API | Label UI | Semántica |
|---|---|---|
OPERADOR | Operadora | Único rol autenticado v1 |
[DECISIÓN] No hay VECINA / JUNTA / BRIGADA / COLORISTA.
ReportStatus
| Status API | Label UI | Semántica |
|---|---|---|
RECIBIDO | Recibido | Entró; nadie lo ha tomado |
CURSO | En curso | Nuria lo atiende; no es resuelto |
CERRADO | Cerrado | La mesa lo da por hecho; no afirma la farola encendida |
Orden de columnas: Recibido → En curso → Cerrado.
Orden dentro de columna: createdAt descendente (lo último arriba). [DECISIÓN]
Kind
| API | Label UI |
|---|---|
farola | Farola |
cubo | Cubo |
banco | Banco |
acera | Acera |
otro | Otro |
Bandos (líneas seed)
| id | Título | Semántica |
|---|---|---|
bnd_1 | Corte de agua | Aviso de servicio |
bnd_2 | Recogida de enseres | Aviso de servicio |
bnd_3 | Esto no es el 112 | Límite de producto |
[COMPROBADO] Tres bandos del encargo. [DECISIÓN]
Partes (líneas seed)
| code | kind | status | Autora/autor |
|---|---|---|---|
PG-2401 | farola | RECIBIDO | Rosa Méndez |
PG-2402 | cubo | RECIBIDO | Andrés Peña |
PG-2403 | acera | RECIBIDO | Rosa Méndez |
PG-2404 | banco | CURSO | Andrés Peña |
PG-2405 | farola | CURSO | Rosa Méndez |
PG-2406 | cubo | CERRADO | Andrés Peña |
PG-2407 | otro | CERRADO | Rosa Méndez |
[COMPROBADO] Siete partes, 3/2/2, del encargo.
6. Modelo mental vs UI
| Modelo mental | Representación |
|---|---|
| «Le digo al distrito lo de la farola» | Chat / + compositor |
| «¿Me han hecho caso?» | Acuse con código; no tablero |
| «¿Ya está arreglado?» | No. Copy: enviado ≠ resuelto |
| «¿Es una emergencia?» | No. Copy: PREGÓN no es 112 |
| «¿Qué me ha llegado hoy?» | Mesa, columna Recibido |
| «Lo estoy atendiendo» | Columna En curso |
| «La brigada terminó» | Columna Cerrado (mesa, no milagro) |
| «¿Tengo cita?» | No hay UI. Copy: PREGÓN no cita |
| «¿Cuál es el expediente?» | No hay UI. Copy: mensaje ≠ expediente |
| «Salgo de la mesa» | Rail → Salir |
7. Query string y estado
| Ruta | Query | Uso |
|---|---|---|
/login | ?next=/mesa o /mesa/PG-2401 | Redirect post-login sanitizado |
/mesa | ?col=recibido|curso|cerrado (Should) | Foco de columna; Must en 390 (UI-11) |
| Resto | — | Sin ?q= de búsqueda Must |
[DECISIÓN] Sin búsqueda facetada en v1. 7 partes se recorren a ojo. L+1: filtro por tipo.
El compositor no persiste borrador en sessionStorage como Must. Si se sale sin enviar, se pierde. Could L+1: draft local.
/ok/:code no hace GET JWT. El código vive en la URL. Detalle de calle/tipo: state del POST si existe. [DECISIÓN] D9.
8. Rutas API alineadas a IA
| UI | API |
|---|---|
| Chat / bandos | GET /api/bandos público |
| Enviar parte | POST /api/reports público |
| Login | POST /api/auth/login |
| Mesa | GET /api/reports JWT |
| Ficha | GET /api/reports/:code JWT |
| Mover columna | PATCH /api/reports/:code JWT |
| Salud | GET /api/health · service: "pregon-api" |
[COMPROBADO] Contrato del encargo. No se documentan citas, 112 ni POST /api/expedientes como Must.
GET /api/me es Could (el login ya devuelve user). Mesa puede pintar «Nuria Delgado» desde pregon_user en storage.
9. Decisiones de IA descartadas
| Idea | Por qué no en L1 v1 |
|---|---|
/explorar mapa de farolas | Rompe S4 / H-CHAT; eso es GIS |
| Área «mis partes» con cuenta | Cuenta de facto; contradice H3 |
| Feed de partes ajenos en el chat | Privacidad de calle; foro vecinal |
| Search como home | H-SEARCH de otros días |
| Hoy bento de sillas | H-BENTO de MATIZ |
| Wizard de 3 pasos (zona / tope) | ZAGUÁN |
| Lookbook editorial | H-GALLERY de MARGEN |
/cita / /112 | Rompe microcopy |
| Bottom tabs Chat · Mesa · Cuenta | Mezclaría oficios; el vecino no tiene mesa |
| Top-nav Servicios / Sede / Actualidad | Marketing municipal; contradice el brief |
04-user-flows.md
04 — Flujos de usuario — PREGÓN
Convenciones
- Actor: Guest (vecina/vecino anónimo) | OPERADOR
- Éxito: resultado observable
- Errores: UI + código HTTP cuando aplica
- Flujo canónico vecino: F-CHAT (hilo → composer → POST parte → ok)
- Flujo canónico oficio: F-KAN (login → mesa → ficha → PATCH status)
- Junta / 112: no tienen flujo de sistema
F0 — Entrada pública (Guest)
/ → GET /api/bandos → hilo + composer
/ok/:code → acuse (sin GET JWT)
/login → si hay token válido → /mesa (o next)
/mesa sin token → /login?next=/mesa
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Aterriza en el chat | Router público |
| 2 | Lee bandos; ve compositor | UI-01 / UI-02 |
| 3 | No encuentra «crear cuenta» ni «pedir cita» | IA |
Éxito: cara pública = hilo.
AC: no hay landing, hero 2-col ni Cl@ve. Chat-first.
F1 — Leer bandos (Guest)
/ → GET /api/bandos
→ 3 bandos anclados | empty | error | skeleton
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee corte de agua, enseres, «no es 112» | bandos |
| 2 | Entiende que el hilo es del distrito | UI |
Éxito: 3 bandos reales en el fold, o empty accionable (composer visible).
AC: cada bando muestra título + cuerpo. El compositor no espera a que carguen para mostrarse (Should: se muestra; Enviar espera).
Guión Rosa (demo): bnd_1 agua · bnd_2 enseres · bnd_3 no es 112. [SUPUESTO de catálogo] alineado al seed de doc 05.
| Caso | Comportamiento |
|---|---|
| 0 bandos | UI-08: «Aún no hay bandos del distrito.» + compositor |
| Red / 5xx | UI-09 + Reintentar (repite GET) |
| Primera carga | Skeleton hilo (doc 16) |
F2 — Enviar parte (Guest) · F-CHAT
/ composer
→ street, kind, body, authorName
→ POST /api/reports
→ 201 Report { code, status: RECIBIDO, ... }
→ /ok/:code
| Campo | Validación cliente (mín.) | API |
|---|---|---|
| authorName | required, min 2, max 80 | @MinLength(2) |
| street | required, min 4, max 120 | @MinLength(4) |
| kind | required, enum | farola | cubo | banco | acera | otro |
| body | required, min 10, max 500 | @MinLength(10) |
| status | no en POST; siempre RECIBIDO | servidor fuerza RECIBIDO |
| code | no en POST | servidor asigna PG-XXXX |
Éxito: registro status=RECIBIDO + pantalla acuse.
No hay «Resuelto» ni «Citar» en v1.
JSON camelCase: authorName (el brief nombra author_name en SQL). [DECISIÓN]
| Gestos | Resultado |
|---|---|
| Chip tipo | Radio único; aria-pressed |
| Enviar | POST; pending en botón |
| Inválido | Inline; no POST |
| Doble submit | Botón disabled mientras pending |
Errores
| Caso | Comportamiento |
|---|---|
| Validación DTO | 400 + mensaje de campo |
| Red caída | Error de red en form; no navegar a /ok/… |
| 429 | Should: «Espera un momento para escribir otro.» |
| Kind fuera de enum | 400 |
[DECISIÓN] Crear siempre como RECIBIDO. Cover no viaja en el POST v1 (seed sí tiene cover).
[COMPROBADO] Entidad Report según encargo (code, street, kind, body, status, author_name).
F3 — Acuse (Guest)
/ok/:code → muestra código de la URL
→ si hay router state del POST: calle / tipo
→ “Enviado no es resuelto. El acuse no apaga la farola.”
→ “PREGÓN no es 112.”
→ CTA escribir otro → /
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee código y copy | UI-04 |
| 2 | No abre la mesa | Guard |
| 3 | Puede escribir otro | Router |
AC: el disclaimer es visible sin scroll en desktop.
No hace GET /api/reports/:code (JWT). Si se recarga, queda el código + copy; no se inventa el parte. [DECISIÓN] D9.
Código con formato inválido (no PG- + 4 dígitos): mismo acuse genérico; no 404 de producto público.
F4 — Login JWT (OPERADOR)
/login → POST /api/auth/login { email, password }
→ 200 { accessToken, user } → localStorage → /mesa (o ?next sanitizado)
→ 401 → mensaje error en form
| Campo | Validación cliente | API |
|---|---|---|
| required, email | @IsEmail() | |
| password | required, min 6 | @MinLength(6) |
Éxito: token guardado; user role=OPERADOR, nombre Nuria Delgado.
Credencial demo: nuria@pregon.ayto / password123.
| Caso | Comportamiento |
|---|---|
| Credenciales inválidas | 401 + mensaje |
| Red caída | Error de red en UI |
| Token caducado en mesa | 401 en GET → re-login |
next externo / // | Ignorado; va a /mesa |
| Vecina en login | No hay signup; link «Volver al chat» |
[COMPROBADO] Credencial del encargo.
F5 — Mesa kanban (OPERADOR) · F-KAN
/mesa (token)
→ GET /api/reports
→ { recibido[3], curso[2], cerrado[2] }
→ tablero | empty por columna | error | skeleton
→ tap card → /mesa/:code
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee las 3 columnas | reports agrupados |
| 2 | Lee código, tipo, calle, autora en cada card | ReportCard |
| 3 | Abre ficha | Router |
Éxito: 3/2/2 en el seed, o empty por columna.
AC cards: cada una muestra código, tipo, calle. Cero botones Citar / 112 / Expediente.
Guión Nuria (demo): Recibido PG-2401 farola Rosa · PG-2402 cubo Andrés · PG-2403 acera Rosa. En curso PG-2404 banco · PG-2405 farola. Cerrado PG-2406 cubo · PG-2407 otro. [SUPUESTO de catálogo]
| Caso | Comportamiento |
|---|---|
| Columna 0 | UI-08 de esa columna |
| Red / 5xx | UI-09 + Reintentar |
| Primera carga | Skeleton 3 columnas (doc 16) |
| Sin token | Redirect /login |
| 401 mid-session | Redirect /login |
Query opcional: GET /api/reports?status=RECIBIDO (plano) o ?view=flat. Default: agrupado. [DECISIÓN]
[DECISIÓN] GET /api/reports es el Must de producto. No hay drag-and-drop Must: se cambia status en la ficha. Could: drag entre columnas en desktop.
F6 — Ficha de parte (OPERADOR)
/mesa/:code → GET /api/reports/:code
→ foto, código, calle, tipo, cuerpo, autora, fecha, status
→ “El mensaje no es un expediente. PREGÓN no cita.”
→ chips Recibido / En curso / Cerrado
→ PATCH /api/reports/:code { status }
→ 200 Report
→ 404 code inexistente → mensaje + volver a /mesa
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee el parte | Report |
| 2 | Mueve columna | PATCH |
| 3 | Lee disclaimer | UI |
Éxito: ficha leíble; disclaimer visible; status actualizado.
Error: 404 si code inexistente; 401 sin token.
AC ficha
| # | Criterio |
|---|---|
| 1 | Foto del cover si existe (assets/partes/:file); si no, placeholder de tipo |
| 2 | Código en Ibarra Real Nova; calle en Jost |
| 3 | Cuerpo completo visible |
| 4 | Disclaimer «mensaje ≠ expediente» visible sin scroll en desktop |
| 5 | Sin CTA de cita, 112-como-envío, expediente SIA |
Transiciones permitidas
| Desde | Hacia |
|---|---|
| RECIBIDO | CURSO, CERRADO |
| CURSO | RECIBIDO, CERRADO |
| CERRADO | CURSO (reabrir Should) |
[DECISIÓN] v1 permite cualquier transición entre los 3 estados (mesa humana, no workflow SIA). PATCH solo acepta { status }. No acepta editar calle/cuerpo (el parte es lo que escribió el vecino).
F7 — Mesa mobile (OPERADOR) · UI-11
/mesa ~390
→ segmentos Recibido | En curso | Cerrado
→ lista de la columna activa
→ tap fila → /mesa/:code
Misma API que F5. No se aplastan 3 columnas. El rail S-SIDE se convierte en segmentos. [DECISIÓN]
F8 — Logout (OPERADOR)
Mesa / rail → Salir → limpia localStorage → /login
| Caso | Comportamiento |
|---|---|
| Salir | Borra pregon_token + pregon_user |
Tras salir, /mesa | Redirect login |
Tras salir, / | Chat público intacto |
No hay pantalla /cuenta Must (MATIZ sí la tenía; aquí el oficio cabe en el rail). [DECISIÓN]
F9 — Health (ops)
GET /api/health → 200 { ok: true, service: "pregon-api" }
Sin auth. No es una pantalla. Smoke de arranque.
[COMPROBADO] El leftover actual responde service: "matiz-api" y puerto 3019. El contrato de esta suite es pregon-api :3020.
Matriz de errores global
| Código | Cuándo | UI |
|---|---|---|
| 400 | DTO inválido | Mensaje campo / genérico |
| 401 | Sin/mal token en mesa | Re-login |
| 404 | code no existe (JWT) | Mensaje + volver a Mesa |
| 429 | Rate limit POST | Espera |
| 5xx / network | API caída | Error + retry |
Flujos fuera de alcance v1
- Registro / recuperación de password de OPERADOR
- Cuenta de vecino / «mis partes» / push
- Cita o «te llamamos»
- 112 / derivación operativa
- GIS / mapa / coordenadas
- Foto en el POST
- Drag-and-drop de kanban (Could)
- Chat entre vecinos
- Nota interna a brigada
- Multi-operador / turnos
05-data-model.md
05 — Modelo de datos — PREGÓN
1. Visión general
Dominio L1 de pregón municipal + mesa autenticada:
| Entidad | Tabla SQL | Propósito |
|---|---|---|
| User | users | Identidad de oficio (rol OPERADOR) |
| Session | sessions | Sesión JWT / token persistido (Should) |
| Bando | bandos | Mensaje anclado del distrito en el chat público |
| Report | reports | Parte vecino (código PG-XXXX) |
Base: PostgreSQL (Neon project empty-surf-15969880) · IDs: cuid() o texto estable (usr_nuria, bnd_1, PG-2401).
[DECISIÓN] ensureSchema o Prisma son válidos si el contrato de tablas se cumple; el brief de stack no exige Prisma.
2. Enums (aplicación; persistidos como TEXT + check)
Role
| Valor | Descripción |
|---|---|
OPERADOR | Operadora de la mesa; único rol autenticado v1 |
ReportStatus
| Valor | Descripción |
|---|---|
RECIBIDO | Entró; nadie lo ha tomado |
CURSO | En curso; no implica resuelto en la calle |
CERRADO | La mesa lo da por hecho |
Kind
farola | cubo | banco | acera | otro
3. Diagrama ER (texto)
users
id, email, password_hash, name, role(OPERADOR)
district_name
created_at, updated_at
1 ──< sessions
sessions
id, user_id → users
token_hash, expires_at
created_at
bandos
id
title, body
pinned (boolean)
created_at, updated_at
reports
id
code (unique, PG-XXXX)
street, kind, body
status (RECIBIDO|CURSO|CERRADO)
author_name
cover -- filename, nullable
created_at, updated_at
v1: una sola OPERADOR opera sobre el conjunto global.
No hay FK de vecina-usuaria: la vecina no se loguea.
reportsno apunta ausers: el parte es anónimo de cuenta.
4. Tablas
users
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| TEXT | UNIQUE NOT NULL | |
| password_hash | TEXT | NOT NULL (bcrypt) |
| name | TEXT | NOT NULL |
| role | TEXT | NOT NULL DEFAULT 'OPERADOR' |
| district_name | TEXT | NOT NULL DEFAULT 'Distrito Usera / Villaverde' |
| created_at | TIMESTAMPTZ | DEFAULT now() |
| updated_at | TIMESTAMPTZ | DEFAULT now() |
sessions
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| user_id | TEXT | NOT NULL FK → users(id) ON DELETE CASCADE |
| token_hash | TEXT | NOT NULL |
| expires_at | TIMESTAMPTZ | NOT NULL |
| created_at | TIMESTAMPTZ | DEFAULT now() |
[DECISIÓN] El JWT viaja en Authorization: Bearer. sessions permite invalidar (logout) sin esperar expiración. Si el slice usa JWT stateless, documentar el hueco; D-P1-05 se cumple igual con guard en mesa.
bandos
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| title | TEXT | NOT NULL |
| body | TEXT | NOT NULL |
| pinned | BOOLEAN | NOT NULL DEFAULT true |
| created_at / updated_at | TIMESTAMPTZ |
[DECISIÓN] GET público lista pinned = true orden created_at asc (el pregón se lee de viejo a nuevo, como un hilo). L+1: bandos no anclados.
reports
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| code | TEXT | UNIQUE NOT NULL · ^PG-\d{4}$ |
| street | TEXT | NOT NULL |
| kind | TEXT | NOT NULL CHECK IN (farola,cubo,banco,acera,otro) |
| body | TEXT | NOT NULL |
| status | TEXT | NOT NULL DEFAULT 'RECIBIDO' |
| author_name | TEXT | NOT NULL |
| cover | TEXT | NULL · filename (farola.jpg) |
| created_at / updated_at | TIMESTAMPTZ |
[DECISIÓN] SQL snake_case (author_name); JSON camelCase (authorName).
5. Reglas de integridad y negocio
| Regla | Implementación |
|---|---|
| Lectura de bandos | Pública |
| Alta de parte | Pública · status forzado RECIBIDO · code asignado |
| Lectura de mesa / ficha | JWT OPERADOR → 401 sin token |
| PATCH status | JWT OPERADOR → 401 sin token |
| Login / health | Públicos |
| Password | Nunca en claro; solo password_hash |
| Orden bandos | created_at asc, pinned true |
| Orden reports en columna | created_at desc |
| Orden grouped | recibido / curso / cerrado |
| POST Report | no marca CERRADO; no cita; no llama al 112 |
| Público nunca lista reports | no hay GET público de partes |
| Cover allowlist | filenames seed; POST v1 no sube foto |
| Código | secuencial PG- + 4 dígitos; seed hasta 2407 → siguiente 2408 |
6. Contratos API (resumen)
JSON camelCase hacia el web. [DECISIÓN]
GET /api/health
Response 200: { "ok": true, "service": "pregon-api" }
[COMPROBADO] El leftover responde service: "matiz-api". El contrato de esta suite es pregon-api.
GET /api/bandos
Público.
Response 200: Bando[]
[
{
"id": "bnd_1",
"title": "Corte de agua",
"body": "Jueves 21, de 10 a 14 h, en Marcelo Usera, tramo plaza Elíptica.",
"pinned": true,
"createdAt": "2026-08-18T08:00:00.000Z"
}
]
Si 0: [] + empty UI-08.
POST /api/reports
Público.
Body
| Campo | Tipo | Req |
|---|---|---|
| street | string 4–120 | sí |
| kind | farola | cubo | banco | acera | otro | sí |
| body | string 10–500 | sí |
| authorName | string 2–80 | sí |
Response 201
{
"code": "PG-2408",
"street": "Plaza Elíptica, banco de la plaza",
"kind": "farola",
"body": "La farola del banco no enciende desde anoche.",
"status": "RECIBIDO",
"authorName": "Rosa Méndez",
"cover": null,
"createdAt": "2026-08-20T09:12:00.000Z"
}
DTO inválido → 400. No JWT.
POST /api/auth/login
Body: { email, password }
Response 200: { accessToken, user: { id, email, name, role, districtName } }
401 credenciales inválidas. Response sin hash.
GET /api/reports (JWT)
Response 200 agrupada (default)
{
"recibido": [ { "code": "PG-2401", "street": "…", "kind": "farola", "body": "…", "status": "RECIBIDO", "authorName": "Rosa Méndez", "cover": "farola.jpg", "createdAt": "…" } ],
"curso": [ ],
"cerrado": [ ]
}
Query:
| Query | Resultado |
|---|---|
| (ninguna) | agrupado recibido / curso / cerrado |
?view=flat | Report[] newest first |
?status=RECIBIDO | Report[] filtrado (plano) |
Sin token → 401.
GET /api/reports/:code (JWT)
Report completo o 404. Sin token → 401.
PATCH /api/reports/:code (JWT)
Body: { status: "RECIBIDO" \| "CURSO" \| "CERRADO" }
Response 200: Report actualizado.
No acepta street, body, kind, authorName.
Sin token → 401. Code inexistente → 404. Status inválido → 400.
[COMPROBADO] PATCH sobre :code. [DECISIÓN] solo status.
GET /api/me (Could)
JWT. { id, email, name, role, districtName }. El login ya trae user; no es Must del encargo.
7. Seed de referencia (2026-08-20)
OPERADOR
| Nombre | Password | Distrito | |
|---|---|---|---|
| Nuria Delgado | nuria@pregon.ayto | password123 | Distrito Usera / Villaverde |
[COMPROBADO] Credencial del encargo. Nombre de pila es constructo de diseño.
Bandos (3 · pinned)
| id | Título | Cuerpo |
|---|---|---|
bnd_1 | Corte de agua | Jueves 21, de 10 a 14 h, en Marcelo Usera, tramo plaza Elíptica. |
bnd_2 | Recogida de enseres | Sábado 23, de 9 a 14 h. Dejadlos junto al cubo, no en el banco. |
bnd_3 | Esto no es el 112 | PREGÓN es para farolas, cubos, bancos y aceras. Si hay una emergencia, llamad al 112. |
Reports (7)
Calles, cuerpos y fotos son catálogo demo, no partes oficiales de una junta. [SUPUESTO de catálogo]
| code | kind | status | Autora/autor | Calle | Cover | Cuerpo (demo) |
|---|---|---|---|---|---|---|
| PG-2401 | farola | RECIBIDO | Rosa Méndez | Plaza Elíptica, banco de la plaza | farola.jpg | La farola del banco no enciende desde anoche. Estoy sentada debajo y no hay luz. |
| PG-2402 | cubo | RECIBIDO | Andrés Peña | Calle de Marcelo Usera 42 | cubo.jpg | Cubo de orgánicos rebosando. Lleva dos días. Huele. |
| PG-2403 | acera | RECIBIDO | Rosa Méndez | Av. de Rafaela Ybarra, esquina Clemente | acera.jpg | Baldosa suelta. Casi me caigo al bajar del bordillo. |
| PG-2404 | banco | CURSO | Andrés Peña | Plaza de Ágata, banco del olivo | banco.jpg | Tabla del asiento rota. Hay clavos. |
| PG-2405 | farola | CURSO | Rosa Méndez | Calle de la Resina 18, paso de cebra | farola.jpg | La farola parpadea junto al paso. De noche da miedo cruzar. |
| PG-2406 | cubo | CERRADO | Andrés Peña | Av. de Andalucía, frente al mercado | cubo.jpg | Cubo de envases tumbado. (Mesa: recolocaron.) |
| PG-2407 | otro | CERRADO | Rosa Méndez | Calle de Amparo, muro del colegio | acera.jpg | Valla caída junto al colegio. No es farola ni cubo. |
Conteos: 3 RECIBIDO · 2 CURSO · 2 CERRADO.
Rosa firma 4; Andrés firma 3.
created_at demo (Europe/Madrid, 2026-08-20):
| code | Hora demo |
|---|---|
| PG-2407 | 08:05 · ya cerrado |
| PG-2406 | 08:20 · ya cerrado |
| PG-2405 | 08:40 · en curso |
| PG-2404 | 09:00 · en curso |
| PG-2403 | 09:15 · recibido |
| PG-2402 | 09:30 · recibido |
| PG-2401 | 09:45 · recibido (el más nuevo; top de Recibido) |
Las siete partes y los tres bandos son seed de producto. No se presentan como registro oficial de Usera o Villaverde. [SUPUESTO de catálogo]
Usera y Villaverde son dos distritos reales de Madrid; la demo los trata como una mesa. No se afirma una junta unificada. [DECISIÓN]
8. Evolución posible (no implementada)
| Cambio | Nivel |
|---|---|
Acuse público mínimo { code, status } | L+1 |
| Foto en POST (allowlist / upload) | L+1 |
| Nota interna de Nuria | L+1 |
| Segundo OPERADOR | L+1 / L3 |
| Soft delete + audit log | L3 |
| GIS / mapa | Fuera (S4) |
| Cuenta de vecino | Fuera (H3) |
| Expediente SIA / 112 | Fuera (microcopy) |
06-tech-stack.md
06 — Stack tecnológico — PREGÓN
1. Visión
| Capa | Tecnología | Notas |
|---|---|---|
| Frontend | Angular standalone + signals | Puerto 4200 · web responsive |
| Estilos | Tailwind CSS | Tokens highlighter (fog, tinta, mark, card, grafito, lacre) |
| Backend | NestJS TypeScript | Puerto 3020, prefijo /api |
| Persistencia | PostgreSQL (ensureSchema o Prisma) | Tablas users, bandos, reports (+ sessions Should) |
| DB | Neon PostgreSQL | project empty-surf-15969880 |
| Auth | JWT | Rol OPERADOR (mesa); chat y POST reports públicos |
| Diseño | Paper | 01M0EZQ31KF04PAQ5RMCV7DQ2F |
| Repo | /Users/cristian/orca/pregon-app/ | GitHub Criscode2022/pregon-app |
Stack fijo del cron (D-P0-03). No React/Next/Firebase.
[COMPROBADO] Puerto 3020, Neon, Paper y repo constan en el encargo.
2. Estructura
pregon-app/
├── apps/api/ # Nest (paquete independiente)
├── apps/web/ # Angular + Tailwind (paquete independiente)
└── package.json # scripts api / start · apps independientes
D-P1-03: npm --prefix o pnpm --filter. Prohibido npm workspaces que rompen Angular (AP-06).
Scripts raíz esperados:
| Script | Acción |
|---|---|
| api | Arranca Nest en :3020 |
| start / web | Arranca Angular en :4200 |
3. API pública vs JWT
| Método | Ruta | Auth |
|---|---|---|
| GET | /api/health | Público · { ok: true, service: "pregon-api" } |
| GET | /api/bandos | Público |
| POST | /api/reports | Público |
| POST | /api/auth/login | Público |
| GET | /api/reports | JWT OPERADOR |
| GET | /api/reports/:code | JWT OPERADOR |
| PATCH | /api/reports/:code | JWT OPERADOR |
CORS: origen http://localhost:4200 en local.
[COMPROBADO] Superficie API del encargo. No se añaden Must extra (/me, /expedientes, /geo).
4. Justificación
| Elección | Razón |
|---|---|
| Angular + Nest | Alineación con la serie daily y el handoff del estudio |
| Neon serverless | Volumen bajo (S1: 8–20 partes/día; demo 7 + 3 bandos); no cola Redis |
| JWT OPERADOR | D-P1-05: mesa interna cerrada; el chat sí es público |
| Auth en servidor para mesa | Source of truth = guard; el cliente no «esconde» partes |
| Puerto 3020 | Evitar colisión con MATIZ :3019 / ZAGUÁN :3018 / MARGEN :3017 |
| Web (no PWA Must) | S2: móvil en la acera; install Could L+1 |
code PG-XXXX | Acuse humano, no UUID en la plaza |
5. Variables
.env local (nunca git):
| Variable | Servicio | Descripción |
|---|---|---|
DATABASE_URL | API | Neon empty-surf-15969880 |
JWT_SECRET | API | Firma tokens |
PORT | API | opcional, 3020 |
JWT_EXPIRES | API | opcional (p. ej. 12h) |
.env.example solo placeholders.
Web: URL de API en ApiService (default http://localhost:3020/api). Proxy Angular proxy.conf.json → :3020.
[COMPROBADO] Leftover web apunta a :3019 (environment.ts, proxy.conf.json). Contrato: 3020.
6. Dependencias de producto (mínimas)
API
@nestjs/common/core/platform-express- JWT (
joseo@nestjs/jwt) bcrypt(obcryptjs)- cliente Postgres (
pg/@neondatabase/serverless) o Prisma class-validator+class-transformer
Web
- Angular standalone
- Tailwind 3
- fuentes: Ibarra Real Nova + Jost (Google Fonts o self-host)
7. Arranque (contrato)
cd /Users/cristian/orca/pregon-app
# instalar apps independientes (npm --prefix o pnpm --filter)
# apps/api/.env → DATABASE_URL + JWT_SECRET + PORT=3020
# migrate / ensureSchema + seed
# api → http://localhost:3020
# web → http://localhost:4200
8. Lo que este stack no es
| No | Por qué |
|---|---|
| npm workspaces | AP-06 |
| Firebase Auth | Stack fijo JWT + Neon |
| Next.js / React | D-P0-03 |
| Cl@ve / certificado | PREGÓN no es sede |
| GIS / PostGIS Must | S4: calle en texto |
| Elasticsearch | 7 partes no lo necesitan |
| SIA / tramitador | Mensaje ≠ expediente |
| 112 / CAD | PREGÓN no es 112 |
9. Nota sobre el repo existente
[COMPROBADO] Existe /Users/cristian/orca/pregon-app y el remoto previsto Criscode2022/pregon-app.
El scaffold es leftover de MATIZ: módulos clients, formulas, hoy, sittings; rol COLORISTA; health service: "matiz-api"; puerto default 3019; Tailwind chapel (yeso / amatista / Libre Baskerville / Nunito Sans); rutas /fichas, /cuenta; index.html título MATIZ; docs/neon.md describe tablas de colorista.
Esta suite define el contrato PREGÓN; el código debe alinearse a las rutas y API de los docs 03–05, no al leftover. [DECISIÓN]
07-creative-direction.md
07 — Dirección creativa — PREGÓN
1. Concepto
PREGÓN es un bando marcado con fluorescente: se entra al hilo y se sale con un código, no una landing de «ayuntamiento digital». El ground es fotocopia fría. El mark es el rotulador del tablón. El lacre sella lo urgente, poco.
Mood elegido: highlighter.
Primer instinto descartado: chalky (pizarra municipal). Un bando se marca con fluorescente, no con tiza. La tiza ya fue PIZARRA.
[COMPROBADO] Mood, ground y paleta salen de docs/00-day-brief.md.
2. Territorio visual
| Eje | Sí | No |
|---|---|---|
| Superficie | Fog #F1F3F6, ficha blanca, tinta de sello | Yeso MATIZ, hormigón ZAGUÁN, papel MARGEN, musgo CESTA, cromo RONDA |
| Tipo | Ibarra Real Nova display + Jost UI | Libre Baskerville, Nunito, Instrument, Sora, Cormorant, Public Sans, Spectral, Manrope |
| Foto | Farola, cubo, banco, acera: calle real, luz plana | Smart-city drone, «familia feliz en la plaza», iconos 3D de farola |
| Grid | Hilo de chat + compositor; mesa 3 columnas + rail | Hero 2-col + 3 cards de features (AP-12) |
| Acento | Mark #C8F000 en enviar, columna activa, pin de bando | Amatista chapel, óxido inmobiliario, cadmio editorial |
| Chrome | Rail de estados en mesa; chat sin top-nav de marketing | Bottom tabs, masthead de periódico, wizard 1—2—3, sidebar CRM genérica |
3. Paleta
| Token | Hex | Rol |
|---|---|---|
| Fog / G-FOG | #F1F3F6 | Ground de página. Fotocopia fría, no beige. |
| Tinta | #12141A | Texto, wordmark, cuerpo. Casi negro de sello. |
| Mark | #C8F000 | Rotulador: CTA Enviar, columna activa, pin, código marcado |
| Ficha | #FFFFFF | Solo cards (bando, parte, composer). Nunca el fondo de página. |
| Grafito | #5C6370 | Meta, labels, rail idle, hora, calle secundaria |
| Lacre | #C1121F | Acento secundario, poco: «no es 112», sello, urgente |
Jerarquía de color: el fog carga; el mark puntúa; la ficha encuadra el parte. Si el mark pinta fondos enteros, deja de ser rotulador y se vuelve «brand lime». El lacre no decora el header: solo puntual (112, sello). [DECISIÓN]
4. Tipografía
| Uso | Familia | Tamaños de referencia |
|---|---|---|
| Wordmark | Ibarra Real Nova 700 | 24–28 desktop · 22 mobile |
| Titular de chat / mesa | Ibarra Real Nova 700 | 32–40 |
| Código PG-XXXX | Ibarra Real Nova 700 | 28–36 |
| Título de bando | Ibarra Real Nova 700 | 20–24 |
| Cuerpo de bando / parte | Jost 400 | 16–17 / 26–28 lh |
| UI, chips, form, lista | Jost 400/500/600 | 14 / 16 |
| Meta (hora, calle, tipo) | Jost 500 | 12–13, tracking 0.04em, grafito |
Prohibido: Ibarra Real Nova por debajo de 20px (se rompe). Jost en el wordmark (se vuelve startup).
Fuentes vetadas de la serie y del brief: Libre Baskerville / Nunito / Instrument Serif / Sora / Cormorant / Public Sans / Spectral / Manrope / Zilla / Mulish / Newsreader / Outfit / Lora / Karla.
5. Fotografía
Las imágenes del case son el sistema, no un moodboard:
| Asset | Lectura visual | Uso |
|---|---|---|
hero.jpg | Fotocopia / bando / plaza, no smart-city | Cover UX · atmósfera, no hero de marketing en / |
partes/farola.jpg | Farola de barrio, noche o penumbra | PG-2401 · PG-2405 |
partes/cubo.jpg | Cubo de calle | PG-2402 · PG-2406 |
partes/banco.jpg | Banco de plaza | PG-2404 |
partes/acera.jpg | Baldosa / acera | PG-2403 · PG-2407 |
Crop: partes en 4:3 (calle). No recorte 1:1 de retrato MATIZ ni 16:10 editorial. [DECISIÓN] 4:3 de incidente, distinto del 1:1 de ficha de color y del 16:10 de MARGEN.
Tratamiento: saturación real, sin LUT «civic blue», sin cielo HDR. El mark no se superpone como filtro: vive en UI.
hero.jpg vive en UX-00 Cover, no como hero de producto en el chat. [DECISIÓN] AP-21: 3 bandos reales en el fold.
6. Layout
Chat (H-CHAT)
┌─────────────────────────────────┐
│ PREGÓN · Escríbele al distrito │
│ [bando] [bando] [bando] │ hilo
│ │
├─────────────────────────────────┤
│ nombre · calle · tipo · texto │ composer anclado
│ [ Enviar ] │
└─────────────────────────────────┘
- Wordmark + eslogan (una línea, no portada).
- Lead en el fold: 3 bandos (título Ibarra, cuerpo Jost).
- Composer visible sin cazar el CTA.
No hay portada fotográfica a 60vh. [DECISIÓN]
Mesa (S-SIDE + F-KAN)
┌────────┬──────────┬──────────┬──────────┐
│ Recibido 3 │ En curso 2│ Cerrado 2│
│ · PG-2401 │ │ │
│ · PG-2402 │ │ │
│ · PG-2403 │ │ │
└────────┴──────────┴──────────┴──────────┘
Rail izquierdo: estados + contadores + Nuria + Salir.
Tablero: 3 columnas de cards blancas sobre fog.
390: segmentos + lista (UI-11).
Ficha
Foto 4:3 a ~240–320px, no full-bleed. Código → calle → cuerpo → chips de columna. Blancos de ficha, no de landing.
Login
Columna estrecha sobre fog. Sello, no splash municipal.
7. Motion
| Superficie | Spec |
|---|---|
| Hover card | Rule tinta; no elevación 8px ni scale 1.04 |
| Envío | El compositor se vacía; fade 160ms al acuse; off si reduced-motion |
| Cambio de columna | Badge mark, no confetti ni «sirena» |
| Skeleton | Pulso 1.2s en grafito/fog; off si reduced-motion |
Prohibido: parallax de plaza, scroll-jacking, page transitions de 400ms, sticker de «NUEVO» parpadeante, animación de farola que se enciende al enviar (mentiría resuelto).
8. Anti-clichés (serie ALS-2)
| Cliché | Por qué no |
|---|---|
| Pizarra chalky / tiza | Primer instinto del brief; vetado (PIZARRA) |
| Capilla amatista (MATIZ) | Oficio de color, no bando |
| Hormigón + óxido (ZAGUÁN) | Finca, no farola |
| Papel + cadmio kiosco (MARGEN) | Editorial |
| Botanical / musgo (CESTA) | Despensa |
| Chrome / neón (RONDA) | Gaming |
| «Familia feliz en la plaza» stock | Mentira social del «ya está arreglado» |
| Azul institucional 010 | Sede; contradice R-IN highlighter |
| Iconos outline de farola 2014 | Chrome tipográfico + foto real bastan |
9. Tono de marca (visual + verbal, puente a doc 09)
Cercano de plaza y de mesa. Tuteo. Segunda persona cuando habla la UI al vecino. Tercera cuando habla el parte.
El producto se escribe y se mueve de columna. No cita, no es 112, no abre expediente.
10. Criterios de aceptación creativos
- Ground de página =
#F1F3F6. Cards =#FFFFFF. Nunca al revés. - Mark en el CTA Enviar y en la columna activa, no un header lima lleno.
- 3 bandos visibles en el primer viewport mobile (tras wordmark + eslogan).
- Ninguna fuente de la lista vetada. Ibarra Real Nova + Jost.
- Home reconocible como hilo, no como hero 2-col de smart city.
- Success no celebra un «ya está resuelto» ni un 112 atendido.
- Lacre solo en aviso 112 / sello, no en el chrome.
08-design-system.md
08 — Design system — PREGÓN
1. Tokens
--color-fog: #F1F3F6; /* G-FOG ground */
--color-ink: #12141A;
--color-mark: #C8F000; /* rotulador */
--color-card: #FFFFFF; /* cards only */
--color-graphite: #5C6370;
--color-seal: #C1121F; /* lacre, poco */
--font-display: "Ibarra Real Nova", "Iowan Old Style", serif;
--font-ui: "Jost", system-ui, sans-serif;
--radius-card: 2px; /* fotocopia, no pill */
--radius-cta: 2px;
--rule-rail: 2px solid var(--color-mark);
--space: 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64;
Tailwind (referencia):
| Key | Valor |
|---|---|
colors.fog | #F1F3F6 |
colors.ink | #12141A |
colors.mark | #C8F000 |
colors.card | #FFFFFF |
colors.graphite | #5C6370 |
colors.seal | #C1121F |
fontFamily.display | Ibarra Real Nova |
fontFamily.sans | Jost |
[COMPROBADO] Hex y tipo = brief.
[COMPROBADO] Leftover Tailwind = chapel MATIZ. Contrato: tokens de esta tabla.
2. Escala tipográfica
| Token | Familia | Size / lh | Uso |
|---|---|---|---|
display-l | Ibarra 700 | 40 / 44 | Titular mesa desktop · código acuse |
display-m | Ibarra 700 | 28 / 32 | Código card · wordmark |
display-s | Ibarra 700 | 22 / 28 | Título bando 390 |
code | Ibarra 700 | 24 / 28 | PG-XXXX en card |
body | Jost 400 | 16 / 26 | Bando, parte, disclaimers |
ui | Jost 400 | 16 / 24 | Form, lista, composer |
meta | Jost 500 | 13 / 18 | Hora, calle, tipo |
rail | Jost 600 | 12 / 16 · tracking 0.04em | Recibido · En curso · Cerrado |
3. Componentes
Composer (H-CHAT)
| Spec | Valor |
|---|---|
| Posición 390 | Anclado inferior, sobre fog, card blanca |
| Posición desktop | Columna derecha o bloque bajo el hilo, max 720 |
| Campos | authorName, street, body · labels visibles |
| Tipos | 5 chips radio · target ≥44 |
| CTA | Enviar · fondo mark · texto ink · h-44 |
| Disclaimers | «Mensaje ≠ expediente» · «PREGÓN no es 112» sobre el CTA |
Bando (hilo)
┌─────────────────────────────┐
│ ▌ Corte de agua │ pin mark 4px izq + título Ibarra
│ Jueves 21, de 10 a 14 h… │ cuerpo Jost
│ Distrito │ meta grafito
└─────────────────────────────┘
- Fondo card, sin sombra.
- Filete izquierdo 4px mark (anclado).
- No es un
<a>(no navega).
Card de parte (mesa)
| Columna | Contenido |
|---|---|
| Código | Ibarra 24 · PG-2401 |
| Tipo | chip meta |
| Calle | Jost 16 |
| Autora | meta grafito |
Toda la card es un <a> a /mesa/:code. Hover: rule 1px ink. Sin lift.
Rail (S-SIDE)
| Spec | Valor |
|---|---|
| Ancho desktop | 220–260px fijo izquierdo |
| Fondo | Fog con rule 1px grafito 30% |
| Ítem activo | Jost 12 · ink + filete 2px mark izquierda |
| Ítem idle | Grafito |
| Targets | ≥44×44; lista Recibido / En curso / Cerrado + Salir |
390: el rail no se dibuja; hay segmentos (UI-11).
Segmentos mesa mobile (UI-11)
Tres slots iguales: Recibido · En curso · Cerrado. Activo: mark underline 2px. Lista debajo, una columna.
Ficha de parte
| Bloque | Spec |
|---|---|
| Columna | max-width 720px |
| Cover | 4:3, max 320px |
| Código | display-l / display-s |
| Calle + tipo | meta |
| Cuerpo | body |
| Disclaimer | «El mensaje no es un expediente. PREGÓN no cita.» |
| Status | 3 chips · aria-pressed · PATCH |
Botones
| Variante | Uso | Spec |
|---|---|---|
| Primary | Enviar, Entrar | Fondo mark, texto ink, h-44, radius 2 |
| Secondary | Escribir otro, volver | Rule ink, texto ink, fondo fog |
| Ghost | Mesa (en chat), Salir, Reintentar | Texto grafito |
| Seal | Aviso 112 (no es CTA de envío) | Texto seal, no botón de acción primaria |
| Danger | — | No hay borrado Must |
Pending: disabled + aria-busy. No spinner de marca.
Contraste primary: mark #C8F000 + texto ink #12141A (no texto blanco sobre lima). [DECISIÓN]
Form
Labels visibles (no placeholder-only). Inputs h-44, radius 2, rule grafito 40%, foco outline 2px mark offset 2.
Chips de tipo / status: <button aria-pressed>. Selected: mark filete 2px o fondo mark 30%. Idle: card + rule.
Estados
| Estado | Receta |
|---|---|
| Empty chat | Titular Ibarra 28 + 1 línea Jost + compositor visible |
| Empty columna | Titular + 1 línea · «No hay partes en Recibido.» |
| Error | Banner filete lacre + texto + Reintentar |
| Loading chat | 3 bloques bando + barra composer |
| Loading mesa | 3 columnas × 2 cards |
| 404 parte | «Ese parte no está.» + link a Mesa |
4. Grid y breakpoints
| Viewport | Comportamiento |
|---|---|
| ≥1200 | Chat: hilo 7 / composer 5 o hilo max 720 centrado; mesa rail + 3 col |
| 960–1199 | Mesa rail + 3 col compactas |
| 600–959 | Chat 1 col; mesa segmentos + lista |
| ~390 | Chat 1 col, composer anclado; mesa UI-11 |
Padding horizontal: 24 desktop, 16 mobile.
Chat 390: padding-bottom: calc(composer-height + env(safe-area-inset-bottom)) para no tapar el último bando.
5. Iconografía
Mínima. El pregón no es un set de iconos outline de farola.
| Sitio | Recurso |
|---|---|
| Tipos | Texto del chip. Icono trazo 1.5 de 20px opcional |
| Pin bando | Filete mark, no chincheta clip-art |
| Empty | Nada o una rule mark de 32px |
| Status | Texto Recibido / En curso / Cerrado, no semáforo solo-color |
Si hace falta un icono (salir, error): trazo 1.5, 20px, ink. No pack Remix/Lucide a color.
6. Elevación y borde
| Superficie | Tratamiento |
|---|---|
| Página | Fog plano |
| Card / bando / parte | Blanco plano + rule 1px grafito 30% |
| Lista | Filas separadas por rule |
| Modal | No hay modal Must |
Cero sombras shadow-lg. La fotocopia no flota.
7. Mapa componente → ruta
| Componente | Rutas |
|---|---|
ChatThread | / |
BandoBlock | / |
Composer | / |
OkArticle | /ok/:code |
LoginForm | /login |
SideRail | /mesa, /mesa/:code |
KanbanBoard | /mesa |
ReportCard | /mesa |
ParteArticle | /mesa/:code |
StatusChips | /mesa/:code |
EmptyState / ErrorBanner / SkeletonThread / SkeletonKanban | /, /mesa |
8. Criterios de aceptación DS
- Tokens Tailwind = hex del brief, sin yeso MATIZ
#E6E4EDcomo ground. - Cards en blanco; página en fog.
- Primary CTA mark + texto ink (nunca grafito sobre fog como único CTA).
- Ibarra Real Nova no aparece en labels de form ni en el rail.
- Recorte de parte
aspect-[4/3], no 1:1 de retrato ni 16:10 editorial. - Targets de chips, rail, segmentos y CTA ≥44px.
- Composer anclado no tapa el último bando (padding-bottom).
- Fuentes: Ibarra Real Nova + Jost. Cero Libre Baskerville / Nunito leftover.
09-content-guide.md
09 — Guía de contenido — PREGÓN
1. Voz
Cercana de plaza y de mesa. Tuteo en UI al vecino. Segunda persona cuando la interfaz habla. Tercera cuando habla el parte (Rosa escribió…).
Sin jerga de smart city («ciudadanía activa», «tu voz cuenta», «innovamos el barrio»). Sin sede electrónica. Sin «reserva tu slot». Sin sirena.
El producto se escribe y se mueve de columna. No cita, no es 112, no abre expediente.
2. Palabras permitidas / prohibidas
| Usar | No usar |
|---|---|
| Parte / mensaje / código | Expediente, procedimiento, SIA, «ticket IT» |
| Enviado / recibido | Resuelto (al enviar), «ya está arreglado», «cerrado» en acuse |
| En curso / cerrado (mesa) | «Solucionado», «completado al 100%» |
| Bando / pregón / distrito | Newsletter, feed, muro, «novedades del ayto» |
| Farola / cubo / banco / acera | Incidencia GIS, «activo patrimonial», SKU |
| Mesa / operadora | Inbox, CRM, backoffice, kanban (en UI; sí en docs) |
| PREGÓN no cita / no es 112 | Pedir cita, llamar ahora, emergencia (como CTA) |
«Kanban» es lenguaje de diseño, no de UI: la vecina no lee «kanban»; Nuria lee Recibido / En curso / Cerrado.
3. Microcopy duro (literales irrenunciables)
El brief fija oposiciones. Aparecen donde el usuario puede confundirse.
| Oposición | Dónde | Copy |
|---|---|---|
| Mensaje ≠ expediente | Composer sobre Enviar · ficha de parte · pie de mesa | «Esto es un mensaje, no un expediente. PREGÓN no abre un procedimiento.» |
| Enviado ≠ resuelto | Acuse · composer pie | «Enviado no es resuelto. El acuse no apaga la farola.» |
| PREGÓN no cita | Ficha · login pie · empty mesa | «PREGÓN no cita. No hay hora ni “te llamamos a las 10”.» |
| PREGÓN no es 112 | Chat pie · acuse · bando bnd_3 | «PREGÓN no es 112. Si hay una emergencia, llama al 112.» |
[DECISIÓN] Los cuatro textos son obligatorios en UI. No se diluyen a un tooltip.
4. Microcopy por superficie
| Superficie | Copy |
|---|---|
| Eslogan | Escríbele al distrito. |
| Línea de mesa | Distrito Usera / Villaverde |
| Chat titular | PREGÓN |
| Bando meta | Distrito |
| Composer nombre | Tu nombre |
| Composer calle | Calle o plaza |
| Composer texto | Qué pasa |
| Composer tipos | Farola · Cubo · Banco · Acera · Otro |
| Composer submit | Enviar |
| Composer disclaimer 1 | Esto es un mensaje, no un expediente. |
| Composer disclaimer 2 | PREGÓN no es 112. |
| Login titular | Entrar a la mesa |
| Login ayuda | Solo operadora. El vecino escribe en el chat, sin cuenta. |
| Login submit | Entrar |
| Login error | Email o contraseña no valen. |
| Login volver | Volver al chat |
| Empty chat | Aún no hay bandos del distrito. Puedes escribir un parte igual. |
| Empty Recibido | No hay partes en Recibido. |
| Empty En curso | No hay partes en curso. |
| Empty Cerrado | No hay partes cerrados. |
| Success titular | Enviado. |
| Success código | PG-2408 |
| Success cuerpo | Enviado no es resuelto. El acuse no apaga la farola. |
| Success 112 | PREGÓN no es 112. |
| Success CTA | Escribir otro |
| Error | No hemos podido cargar el pregón. |
| Error retry | Reintentar |
| Loading chat | Abriendo el hilo… |
| Loading mesa | Abriendo la mesa… |
| 404 parte | Ese parte no está. |
| Mesa titular | Mesa |
| Rail | Recibido · En curso · Cerrado |
| Ficha volver | ← Mesa |
| Ficha disclaimer | El mensaje no es un expediente. PREGÓN no cita. |
| Ficha status | Recibido · En curso · Cerrado |
| Salir | Salir |
| Link Mesa (chat) | Mesa |
5. Bandos y partes seed (tono)
Los bandos demo se escriben como aviso de tablón, no como tuit institucional ni como crónica.
| Pieza | Tono |
|---|---|
bnd_1 agua | Hora y tramo. Sin «lamentamos las molestias». |
bnd_2 enseres | Dónde dejarlos (cubo, no banco). Oficio. |
bnd_3 112 | Límite. Sin dramatismo. |
| PG-2401 farola | Rosa, desde el banco, concreto. |
| PG-2402 cubo | Andrés, corto, dos días, huele. |
| PG-2403 acera | Rosa, casi me caigo, baldosa. |
| PG-2404 banco | Andrés, clavos, olivo. |
| PG-2405 farola | Rosa, paso de cebra, de noche. |
| PG-2406 cubo | Cerrado de mesa; el cuerpo sigue siendo lo que escribió Andrés. |
| PG-2407 otro | Rosa, valla, colegio; tipo otro. |
No inventar declaraciones de vecinas reales. Si hay una voz, es del parte (neutra) o de persona de diseño etiquetada.
En CERRADO no se reescribe el cuerpo a «arreglado». El cierre es el status de mesa. [DECISIÓN]
6. Nombres propios del seed
| Nombre | Uso |
|---|---|
| Rosa Méndez | VECINA; sin cuenta; autora de 4 partes |
| Nuria Delgado | OPERADOR; nuria@pregon.ayto |
| Andrés Peña | VECINO digital; sin cuenta; autor de 3 partes |
| Distrito Usera / Villaverde | Mesa demo; atrezzo de mercado (dos distritos, una mesa) |
No inventar CIF de junta, código INE, ni teléfono 010 real como si fuera el producto. [SUPUESTO de catálogo demo]
Calles seed (plaza Elíptica, Marcelo Usera, Rafaela Ybarra, plaza de Ágata, Resina, Avenida de Andalucía, Amparo) son topónimos reconocibles, no un inventario oficial de incidencias 2026.
7. Tono por estado
| Status | Frase UI | Frase que Nuria podría decir (fuera de app) |
|---|---|---|
| RECIBIDO | Recibido | «Nos ha llegado. Aún no está en curso.» |
| CURSO | En curso | «Lo estamos viendo. No está resuelto.» |
| CERRADO | Cerrado | «La mesa lo cierra. Si la farola sigue, se escribe otro.» |
La app no envía esos mensajes al vecino en v1. El copy solo prepara el habla. No hay email de «su farola ya luce».
8. Capitalización y números
- Wordmark: PREGÓN en versales Ibarra Real Nova (tilde).
- Código:
PG-2401(PG versales, guion, 4 dígitos). - Nombres: Rosa Méndez, Nuria Delgado, Andrés Peña.
- Tipos en chip: Farola (mayúscula inicial).
- Calles: Plaza Elíptica, Calle de Marcelo Usera (mayúscula de vía).
- Fecha:
20 ago, 09:45en card;jueves 20 de agostosi hay cabecera de mesa. - No mezclar
pg-2401en minúsculas en UI. API puede normalizar a versales.
9. Idioma
es-ES. Fácil lectura. EN no en v1.
No mezclar «report» en UI. En API sí: reports, kind, status, authorName.
«Vecina» en UI cuando hablamos de Rosa; «vecino» para Andrés; «el vecino» genérico en copy de sistema. [DECISIÓN]
10. Accesibilidad de copy
- No transmitir estado solo con color (el mark no «significa» En curso él solo).
- Chips llevan texto: Recibido / En curso / Cerrado.
- Aviso 112 lleva palabras, no solo el lacre.
- Alt de fotos: tipo + lugar, no «imagen1».
- hero: «Fotocopia de bando y plaza; pregón de distrito, no smart-city.»
- farola: «Farola de barrio; parte de alumbrado.»
- cubo: «Cubo de basura en la calle.»
- banco: «Banco de plaza.»
- acera: «Acera y baldosa de barrio.»
11. Piezas que no se escriben
| No | Motivo |
|---|---|
| «Tu expediente ha sido registrado» | Mensaje ≠ expediente |
| «Ya está resuelto» como hecho de enviar | Enviado ≠ resuelto |
| «Te llamamos a las 10» / «Pedir cita» | PREGÓN no cita |
| «Si es urgente, envía aquí» (como 112) | PREGÓN no es 112 |
| Ranking de las calles más rotas | Likes, otro producto |
| Lorem / «lorem ipsum farola» | Prohibido en el case |
| Declaraciones inventadas de una junta real | Verdad de catálogo |
10-accessibility.md
10 — Accesibilidad — PREGÓN
Objetivo: WCAG 2.2 AA. No se declara conformidad legal certificada.
Este documento cubre la UI (chat, acuse, login, mesa, ficha). No audita la accesibilidad física de la plaza Elíptica, de la junta ni del 112. [SUPUESTO]
1. Decisiones
| Tema | Decisión |
|---|---|
| Tipo UI | Jost ≥16px body |
| Display | Ibarra Real Nova no se usa por debajo de 20px |
| Contraste | Tinta #12141A sobre #F1F3F6 / #FFFFFF |
| CTA mark | Texto ink sobre #C8F000 |
| Lacre 112 | Texto seal sobre fog o seal + ink, nunca lacre sobre mark |
| Foco | Outline 2px mark, offset 2px, visible (no outline-none global) |
| Rail | <nav> + aria-current="page" |
| Composer | Labels visibles; chips aria-pressed; form en <form> |
| Imágenes | alt = tipo + lugar |
| Form | Labels visibles, no placeholder-only |
| Errores | Texto, no solo color |
| Mobile | Targets ≥44px (chips, Enviar, segmentos) · S2 banco de la plaza |
| Movimiento | Fade 160ms; prefers-reduced-motion: reduce → 0 |
| Live regions | aria-live="polite" en empty/error de chat y mesa |
2. Contraste (comprobación de diseño)
| Par | Uso | Nota |
|---|---|---|
#12141A / #F1F3F6 | Body sobre fog | Pasa AA y AAA cuerpo |
#12141A / #FFFFFF | Body sobre ficha | Pasa AA y AAA |
#12141A / #C8F000 | CTA Enviar | Pasa AA para texto ≥14px bold; no usar blanco sobre mark |
#5C6370 / #F1F3F6 | Meta grafito | Verificar ≥13px; no usar en 11px |
#5C6370 como texto 12px sobre blanco | — | Prohibido para body |
| Rail idle grafito 50% | Solo si no es el único indicador | + label textual Recibido / En curso / Cerrado |
#C1121F / #F1F3F6 | Aviso 112 | Verificar ≥16px; si falla, ink + rule lacre |
| Blanco / mark como texto | — | Prohibido (lima + blanco falla) |
3. Teclado
| Flujo | Orden |
|---|---|
| Chat | Wordmark → bandos (lectura) → nombre → calle → tipos → texto → Enviar → link Mesa |
| Ok | Código (lectura) → Escribir otro |
| Login | Email → password → submit → volver al chat |
| Mesa | Rail Recibido / En curso / Cerrado / Salir → cards en orden de columna |
| Ficha | Atrás → chips status → lectura cuerpo |
- Enter en chip tipo = selecciona (
aria-pressed). - Enter en card = navegar a ficha.
- Escape no cierra el composer (no es modal).
- Focus trap: no hay modal Must.
- En 390, el composer anclado no debe robar el foco al cargar.
4. Semántica
| Pieza | Markup |
|---|---|
| Chat | <main> · hilo <section aria-label="Bandos del distrito"> |
| Composer | <form aria-label="Escribir un parte"> |
| Tipos | role="group" + aria-label="Tipo" |
| Bandos | <article> por bando; pin decorativo aria-hidden |
| Rail | <nav aria-label="Estados de la mesa"> · activo aria-current="page" |
| Kanban | 3 <section aria-label="Recibido"> etc. |
| Card parte | un <a> por parte; incluye código, tipo, calle en texto |
| Status ficha | role="group" + aria-label="Columna" |
| Aviso 112 | texto normal + borde lacre; no alert de envío |
| Skeletons | aria-busy="true" en el contenedor; aria-hidden en placeholders |
5. Lector de pantalla — copy
| Situación | Anuncio |
|---|---|
| Bando | «Bando. Corte de agua. Jueves 21…» |
| Chip tipo | «Farola, pulsado» / «Cubo, no pulsado» |
| Enviado | «Enviado. Enviado no es resuelto. Código PG-2408.» |
| Empty chat | «Aún no hay bandos del distrito. Puedes escribir un parte igual.» |
| Empty columna | «No hay partes en Recibido.» |
| Error | Anuncio en live region; foco al banner |
| Card mesa | «PG-2401, farola, Plaza Elíptica, banco de la plaza, Rosa Méndez, Recibido» |
| Chip status | «En curso, pulsado» |
6. Producto vs calle física
| Lo que PREGÓN puede hacer | Lo que no afirma |
|---|---|
| Targets 44px en el banco de la plaza | Que el banco de plaza Elíptica sea accesible de hecho |
| Chat usable con teclado, zoom 200% y letra 16px | Que la junta tenga rampa o mostrador accesible |
| Copy «no es 112» | Que el 112 esté operativo ni que el distrito haya hecho un DPIA |
| Autoría con nombre libre | Que Rosa sea identificable de forma legal |
[DECISIÓN] v1 no incluye auditoría a11y de sede ni de espacio público. authorName es dato de parte demo, no un certificado de identidad.
7. Riesgos
| Riesgo | Mitigación |
|---|---|
| Composer tapa el hilo en 390 | padding-bottom del main |
| Ibarra Real Nova en meta 12px | Prohibido; meta = Jost 13 |
| Mark como único estado de columna | + aria-current + label |
| Foto sin alt | Checklist seed: 4 alts + hero |
| 112 leído solo por color lacre | Label siempre |
| Rosa poco digital (S2) | Targets ≥44; no gestos swipe; no hover-only; sin cuenta |
| Chip lima + texto blanco | Texto ink |
| Leftover chapel / Nunito 14px | Tokens PREGÓN |
8. QA a11y (mínimo)
| # | Prueba | Pasa si |
|---|---|---|
| 1 | Teclado chat → composer → ok | Sin trampa; foco visible |
| 2 | Teclado login → mesa → ficha → chip status | Acciones alcanzables |
| 3 | Zoom 200% móvil 390 | Chips usables; composer no solapa inputs |
| 4 | Lighthouse a11y ≥ 90 en / | Sin contrast fails de tokens |
| 5 | VoiceOver/NVDA: bando y card anunciados | Título, código, calle comprensibles |
| 6 | prefers-reduced-motion | Sin stagger ni slide |
| 7 | Contraste CTA mark + aviso 112 | Texto ink sobre mark; 112 con texto |
9. Criterios de aceptación
- Ningún control de chip, rail, segmento o CTA mide menos de 44×44 CSS px.
- El disclaimer no depende del color mark o lacre para ser entendido.
- Las 4 fotos de parte + hero tienen
altno vacío y no genérico. - El foco no se pierde al reintentar un error de red.
- En 390 el composer queda por debajo del hilo (el último bando no queda tapado).
- Una mesa no se anuncia en
/sin token (no hay HTML público de columnas). - Rosa puede completar el composer solo con teclado y sin cuenta.
11-privacy-security.md
11 — Privacidad y seguridad — PREGÓN
1. Datos que existen
| Dato | Dónde | Base / nota |
|---|---|---|
| Email y hash de la operadora | users | Contrato interno de la mesa |
| Nombre de vecina en el parte | reports.author_name | Texto libre; no es una cuenta |
| Calle, tipo, cuerpo | reports | Parte; catálogo seed · PII de atrezzo |
| Foto de incidente | cover filename | Seed; no UGC v1 |
| Bandos | bandos | Mensajes del distrito; no PII |
| Sesión JWT | Authorization + sessions? | Solo OPERADOR |
No se pide cuenta a la vecina. No hay DNI, email ni teléfono de Rosa en producto. No hay tracking de menores. Las fotos seed no se presentan como partes reales.
2. Minimización
| Qué no se recoge en v1 | Por qué |
|---|---|
| Email / teléfono / DNI de la vecina | H3 · no sede · no seguimiento |
| Geolocalización del dispositivo | S4 · calle en texto |
| Coordenadas / GIS | S4 |
| Foto subida por la vecina | No hay UGC Must |
| Hora de cita | PREGÓN no cita |
| Datos de 112 / salud / delito | PREGÓN no es 112 |
| IP como identidad | No se usa como usuario |
authorName es dato de parte demo, no un requisito de identidad legal. L+1 podría hacerlo opcional o «Vecina». [DECISIÓN]
3. Seguridad L1
| Control | Spec |
|---|---|
| Auth mesa | JWT Bearer · role === OPERADOR |
| Password | bcrypt cost 12; nunca en response |
| Lectura mesa | GET reports / :code → 401 sin token |
| Escritura status | PATCH :code → 401 sin token |
| Chat / POST parte / bandos / health / login | Públicos |
| Rate limit | /auth/login + POST /api/reports (anti stuffing / spam; Should si no llega al slice) |
| CORS | Origen http://localhost:4200 en local |
| Secretos | Solo .env (no git) |
| Cover allowlist | Filenames seed; no URL arbitraria |
next post-login | Paths relativos allowlist (/mesa, /mesa/:code) |
| Guard web | Angular en /mesa y /mesa/:code |
| Acuse público | /ok/:code no hidrata GET JWT |
No se declara cumplimiento RGPD definitivo. Sí: minimización, mesa JWT y derecho a borrar la cuenta de operadora (manual L1). Los partes seed se tratan como datos de demo, no como encargo de tratamiento de una junta real.
4. Superficies de riesgo
| Riesgo | Mitigación v1 |
|---|---|
| Enumeración de códigos PG-XXXX | GET :code 401 sin token; acuse no devuelve calle |
| JWT en localStorage | Aceptable en demo; partes son atrezzo, no PII real de junta |
| Nombre de Rosa en seed | El hilo público no lista partes ajenos (D14) |
Foto de calle en public/ | Inevitable para el seed; no hay listado público de partes |
Leftover clients / fichas MATIZ en DB | I01 de build: schema propio, no mezclar |
| Spam de POST reports | Rate limit Should; body max 500 |
| Nuria pide un GIS | S4: no hay fila geo; no se crea |
Vecina pega un teléfono en body | Copy: «qué pasa»; no se pide; no se scrape |
Open redirect next | Allowlist /mesa |
5. Roles y autorización (recordatorio)
| Recurso | Anónimo | OPERADOR |
|---|---|---|
| GET bandos / health / POST reports / POST login | Sí | Sí |
| GET reports / :code | 401 | Sí |
| PATCH status | 401 | Sí |
| Cita / 112 / expediente | No existe | No existe |
Rosa, Andrés y la junta no tienen fila en users.
6. Criterios de aceptación
- Response de login no incluye
passwordnipassword_hash. - GET
/api/reportsy GET/api/reports/PG-2401sin Bearer son 401, no 200 []. - POST
/api/reportssin Bearer es 201 (público). - GET
/api/bandossin Bearer es 200. - No hay formulario público que pida DNI, email o teléfono.
/mesasin token redirige a/login(no renderiza columnas en HTML anónimo)./ok/:codeno llama a GET JWT..envno se commitea.- Health no filtra datos:
{ ok: true, service: "pregon-api" }.
12-analytics.md
12 — Analítica — PREGÓN
North star: % de partes que Nuria mueve a En curso en 24 h (report_create + report_status a CURSO ligado a code, ventana 24 h Europe/Madrid).
Instrumentación modelo. No hay proveedor conectado en v1 salvo que el build añada un logger local. [DECISIÓN] No se inventan cifras de conversión.
1. Eventos
| Evento | Cuándo | Props mín. |
|---|---|---|
chat_view | Render de / | bandos (0–n) |
report_create | POST 201 | code, kind |
ok_view | Render de /ok/:code | code |
operador_login | JWT ok | — |
mesa_view | Render de /mesa | recibido, curso, cerrado |
parte_open | Apertura de ficha | code, status |
report_status | PATCH 200 | code, from, to |
chat_empty | 0 bandos | — |
error_banner | UI-09 | surface |
2. Qué no se mide
| No evento | Motivo |
|---|---|
signup_click / lead_submit | No existe CTA de cuenta vecina |
appointment_booked | PREGÓN no cita |
emergency_call | PREGÓN no es 112; no hay CTA de llamada como envío |
expediente_open | Mensaje ≠ expediente |
report_resolved_on_send | Enviado ≠ resuelto; el evento no existe |
like / favorite | Fuera |
| Identidad de Rosa como usuaria | H3 · no es usuaria |
body completo del parte | PII potencial en texto libre |
Hipótesis H2: ok_view sin resolved_claim (el copy contradice). Si en un test de 5 vecinas alguien dice «ya está arreglado», se anota en research futuro, no como KPI inventado.
3. Funnel modelo
Vecina: chat_view → report_create → ok_view
Operadora: operador_login → mesa_view → parte_open → report_status(CURSO)
Activación vecina = primer report_create.
Activación mesa = primer report_status a CURSO.
North star de sesión de demo: de los RECIBIDO del seed (3) + los nuevos, cuántos pasan a CURSO en 24 h civiles Europe/Madrid.
4. Integridad (producto, no vanidad)
| Señal | Cómo se observa |
|---|---|
| Mesa cerrada | GET /reports 401 sin token (QA, no analytics) |
| Chat abierto | POST reports 201 sin token |
| Expectativa de resuelto | Comentarios cualitativos; cero evento resolved-on-send |
| Expectativa de 112 | Cero evento de llamada como conversión |
| Salud | 4xx/5xx de reports / bandos |
5. Privacidad de analítica
Sin PII extra. code es identificador de parte (PG-2401), no un email. authorName no se envía a un tercero. body no se envía. Ver doc 11.
6. Criterios de aceptación (si se instrumenta)
- Los eventos de §1 tienen nombre estable.
- No se dispara ningún evento de lead / cita / 112-como-conversión / expediente / resuelto-al-enviar.
- Un bloqueador de cookies no rompe el composer (analítica no es Must de runtime).
kindse mide;bodyno.
13-qa-test-plan.md
13 — Plan de pruebas — PREGÓN
1. Smoke obligatorio (D-P1-06)
| # | Acción | Esperado |
|---|---|---|
| 1 | GET /api/health | 200 · { ok: true, service: "pregon-api" } |
| 2 | GET /api/bandos | 200 · 3 ids bnd_1, bnd_2, bnd_3 |
| 3 | POST /api/auth/login nuria@pregon.ayto / password123 | 200 · accessToken · user.role=OPERADOR |
| 4 | GET /api/reports con Bearer | 200 · agrupado 3/2/2 · códigos PG-2401…2407 |
| 5 | ng build o serve web | Web arranca o build OK |
[COMPROBADO] Smoke = health (pregon-api) + bandos públicos + login JWT + reports JWT.
2. Casos funcionales
| ID | Caso | Esperado |
|---|---|---|
| S1 | Códigos seed | GET reports incluye PG-2401…PG-2407 |
| S2 | Reports sin JWT | 401 (no 200 []) |
| S3 | Reports/:code sin JWT | 401 |
| S4 | Bandos sin JWT | 200 |
| S5 | POST reports sin JWT | 201 · status=RECIBIDO · code PG-XXXX |
| S6 | Code inexistente (con JWT) | 404 |
| S7 | Chat UI | / muestra 3 bandos + compositor; cero cuenta / cita |
| S8 | Home es chat | / es H-CHAT; no hero 2-col; no Hoy bento MATIZ |
| S9 | Rutas mesa | /mesa sin token → /login |
| S10 | PATCH status | 200 · RECIBIDO → CURSO |
| S11 | PATCH no edita cuerpo | Body { street: "x" } ignorado o 400 |
| S12 | Login malo | 401 · mensaje en form |
| S13 | Empty chat | 0 bandos → UI-08 + compositor |
| S14 | API caída | UI-09 + Reintentar conserva la vista |
| S15 | Loading | Skeleton hilo / kanban antes del primer paint |
| S16 | Mobile 390 | Composer + chips ≥44px; Enviar no tapado; mesa = lista por columna |
| S17 | Fotos seed | covers farola.jpg cubo.jpg banco.jpg acera.jpg resuelven |
| S18 | Password no viaja de vuelta | login response sin hash |
| S19 | Body POST | acepta street, kind enum, body, authorName |
| S20 | Kind inválido | POST kind farola_led → 400 |
| S21 | Success | /ok/:code copy «Enviado no es resuelto» · «no es 112» |
| S22 | Health no es matiz | service ≠ matiz-api |
| S23 | Conteos seed | recibido 3 · curso 2 · cerrado 2 |
| S24 | /ok/:code sin JWT funciona | 200 de página; no pide login |
| S25 | Microcopy ×4 | mensaje≠expediente · enviado≠resuelto · no cita · no es 112 |
| S26 | Puerto API | proceso en 3020, no 3019 |
| S27 | Leftover | no se sirven /fichas ni /hoy como producto |
3. Datos de prueba
| Uso | Valor |
|---|---|
| OPERADOR | nuria@pregon.ayto / password123 |
| Parte feliz | PG-2401 · Rosa · farola · RECIBIDO |
| Parte en curso | PG-2404 · banco |
| Parte cerrado | PG-2406 · cubo |
| POST feliz | { "street":"Plaza Elíptica, banco","kind":"farola","body":"La farola del banco no enciende desde anoche.","authorName":"Rosa Méndez" } |
| Empty chat | borrar bandos (solo entorno de test) |
| Empty columna | PATCH todos RECIBIDO a CURSO (test) |
4. Regresión de diversidad / craft
| Check | Falla si |
|---|---|
| Home es chat | Hay hero 2-col + 3 cards de marca (AP-12) o bento MATIZ o wizard ZAGUÁN |
| Shell mesa es S-SIDE | Hay bottom tabs como nav primaria de mesa, masthead o chrome 1—2—3 |
| Copy honesto | Acuse dice «resuelto» o ficha dice «expediente» o hay «pedir cita» o CTA 112 de envío |
| Paleta | Se cuelan amatista MATIZ / óxido ZAGUÁN / cadmio MARGEN / tiza PIZARRA |
| Tipo | Libre Baskerville / Nunito / Instrument / Sora / Cormorant / Public Sans / Spectral / Manrope |
| Rutas | Aparecen /fichas, /hoy, /cita, /112, /sede, /expediente, /mapa |
| Ground | Página en yeso #E6E4ED o blanco total en vez de fog #F1F3F6 |
| Health | service: "matiz-api" |
5. Hipótesis (no se «prueban» en la demo)
H1, H2, H3 requieren uso real. El QA verifica que existen las superficies que permitirían medirlas (composer, acuse con disclaimer, kanban 3 columnas, ausencia de signup vecino, 401 mesa).
6. Criterios de salida QA v1
- Smoke §1 en verde (incluido
pregon-api). - S2, S5, S7, S8, S10, S16, S21, S25 en verde.
- Cero blockers de copy deshonesto (expediente / resuelto-al-enviar / cita / 112-como-envío).
- Sin
.enven el repo. - Sin leftover MATIZ como superficie de producto.
7. Fuera de este plan
Playwright e2e completo, carga, fuzzing, auditoría de una junta real.
14-dev-handoff.md
14 — Handoff desarrollo — PREGÓN
1. Arranque
cd /Users/cristian/orca/pregon-app
# apps independientes (npm --prefix o pnpm --filter)
# apps/api/.env
# DATABASE_URL= # Neon empty-surf-15969880
# JWT_SECRET=
# PORT=3020
# migrate / ensureSchema + seed
# api → http://localhost:3020
# web → http://localhost:4200
Demo: nuria@pregon.ayto / password123.
2. Paridad Paper
| Debe verse en Angular | Artboard |
|---|---|
| Tokens highlighter + Ibarra Real Nova / Jost | UI-00 |
| Chat home 1440 bandos + hilo + composer | UI-01 |
| Chat mobile 390 | UI-02 |
| Composer tipos farola/cubo/banco/acera | UI-03 |
| Success código «enviado ≠ resuelto» | UI-04 |
| Login OPERADOR | UI-05 |
| Kanban Recibido / En curso / Cerrado | UI-06 |
| Ficha parte + foto + mover columna | UI-07 |
| Empty | UI-08 |
| Error | UI-09 |
| Loading skeletons | UI-10 |
| Mesa mobile lista por columna | UI-11 |
No clonar MATIZ (Hoy bento + editor + bottom tabs), ZAGUÁN (wizard + comparador), MARGEN (lookbook + masthead), PIZARRA (catálogo search) ni CESTA (tabla + rail de necesidades).
Paper se construye en paralelo; los nombres de artboard son el inventario planificado (doc 00).
3. Contratos
| Tema | Contrato |
|---|---|
| Health | { ok: true, service: "pregon-api" } |
| Bandos | GET /api/bandos público |
| Alta parte | POST /api/reports público · body street, kind, body, authorName |
| Login | { accessToken, user } camelCase (no access_token) |
| Mesa | GET /api/reports JWT → { recibido, curso, cerrado } |
| Ficha | GET /api/reports/:code JWT |
| Status | PATCH /api/reports/:code JWT { status } |
| Status enum | RECIBIDO | CURSO | CERRADO |
| Kind enum | farola | cubo | banco | acera | otro |
| Role | OPERADOR (no COLORISTA, no AGENT) |
| Code | PG-XXXX |
Campos JSON en camelCase hacia el web aunque SQL sea snake_case. [DECISIÓN]
4. Mapa de rutas web
| Path | Guard cliente | Página |
|---|---|---|
/ | no | ChatPage |
/ok/:code | no | OkPage |
/login | no (si hay token → /mesa) | LoginPage |
/mesa | auth | MesaPage |
/mesa/:code | auth | PartePage |
Guard: si no hay token, /login?next=. La API es la autoridad para mesa.
[COMPROBADO] Solo estas rutas. No implementar /fichas, /hoy, /cuenta, /cita, /112.
5. Assets
Copiar desde el case:
2026-08-20-pregon/assets/hero.jpg
2026-08-20-pregon/assets/partes/farola.jpg
2026-08-20-pregon/assets/partes/cubo.jpg
2026-08-20-pregon/assets/partes/banco.jpg
2026-08-20-pregon/assets/partes/acera.jpg
→ apps/web/public/assets/partes/ (hero a public/assets/)
cover del seed = filename (farola.jpg, …).
Quitar leftover public/assets/clients/* y páginas hoy / fichas / ficha / formula / cuenta si siguen en el repo.
[COMPROBADO] El web leftover ya copia partes/*.jpg en public/assets/partes/ y src/assets/partes/. Conservar; no servir como fichas de colorista.
6. DoD implementación L1
| Check | OK |
|---|---|
| API sirve :3020 | |
| Seed 1 OPERADOR + 3 bandos + 7 reports 3/2/2 | |
Smoke health pregon-api + bandos + login + reports | |
| JWT protege GET/PATCH reports; POST reports público | |
| Web chat + ok + login + mesa + ficha | |
| Microcopy ×4 | |
| Tokens Tailwind = paleta brief (no chapel) | |
| Home es chat (no leftover Hoy bento) | |
| Rail Recibido · En curso · Cerrado en mesa | |
| Fuentes Ibarra Real Nova + Jost | |
ng build o equivalente OK | |
| README del repo app con credencial Nuria |
7. No hacer
| Prohibido | Motivo |
|---|---|
| npm workspaces | AP-06 |
Commit de .env | Secreto |
| Prometer expediente, resuelto-al-enviar, cita o 112 | Microcopy duro / AP-09 |
Estado SAVED / APPLIED / PUBLISHED / DRAFT | Vocabulario de otros días |
Role COLORISTA / AGENT / EDITOR | Eso es MATIZ / ZAGUÁN / MARGEN |
| Search bar o wizard como home | Rompe H-CHAT |
| Hero 2-col + 3 cards | AP-12 |
| Bottom tabs Hoy · Fichas · Cuenta | MATIZ leftover |
image_gen como captura de producto | D-P0-09 |
Body POST con oxidant / lines / zona / maxRent | Leftover |
Health matiz-api | Encargo pregon-api |
| Puerto 3019 | Encargo 3020 |
8. Contactos de diseño (artefactos)
| Artefacto | Path |
|---|---|
| Brief | ux-projects/2026-08-20-pregon/docs/00-day-brief.md |
| Paper | https://app.paper.design/file/01M0EZQ31KF04PAQ5RMCV7DQ2F |
| Datos | docs/05-data-model.md |
| Flujos | docs/04-user-flows.md |
| IX | docs/16-interaction-specs.md |
| Build | docs/20-implementation.md |
15-roadmap.md
15 — Roadmap — PREGÓN
Este documento no es un cajón de trabajo L1 a medias.
El vertical slice L1 del brief está especificado como done-scope (docs 01, 18, 19).
Aquí solo viven ideas de L+1 / L3 y pulido posterior.
1. Hecho en L1 (esta ejecución documental)
| Entrega | Evidencia |
|---|---|
| Definición producto + terna | docs 00-brief, 01 |
| Personas Rosa / Nuria / Andrés, JTBD, journey | docs 02 |
| IA chat-first + chrome S-SIDE | docs 03 |
| Flujos F-CHAT + F-KAN | docs 04 |
| Modelo users / bandos / reports + seed | docs 05 |
| Stack Angular/Nest/Neon/JWT :3020 | docs 06, 20 |
| Dirección highlighter + DS | docs 07, 08 |
| Copy Mensaje ≠ expediente · Enviado ≠ resuelto · no cita · no es 112 | docs 09 |
| Paper ref 12 UX + 12 UI | docs 00-paper-reference |
| Suite 00–20 + README + executive | case folder |
2. Implementación L1 (misma complejidad, código)
Contrato en docs 14 y 20. No es deuda silenciosa del case documental: es el build del repo pregon-app. El alcance L1 ya está cerrado en spec; el código alinea leftover MATIZ (clients / formulas / hoy / COLORISTA / :3019 / matiz-api) al contrato.
Eso no se reabre aquí como «próximo trimestre de producto».
3. L+1 (misma familia, subida de pulido)
| Ítem | Notas |
|---|---|
Acuse público mínimo { code, status } | Sigue sin listar el parte entero |
| Foto en POST (allowlist) | Sigue sin GIS |
GET /api/me | Mesa pinta Nuria desde API |
| Nota interna de operadora | No es chat al vecino |
| Rate limit login + POST reports | Anti stuffing / spam |
| Playwright S1–S27 | CI |
| API URL por environment | Deploy |
prefers-reduced-motion verificado | a11y |
| Drag-and-drop columnas desktop | Must sigue siendo chips en ficha |
| Borrador local del composer | sessionStorage; no es enviado |
Estas son subidas de pulido, no parches del L1 documental.
[DECISIÓN] No dejar backlog del vertical slice como «mañana». Lo de arriba es L+1.
4. L3 (requiere brief nuevo de diversidad)
- Multi-operador / turnos / brigada JWT
- Cuenta de vecino y «mis partes»
- GIS / mapa / inventario de farolas
- Expediente SIA / plazos / sede / Cl@ve
- Integración 112 / 010
- Cita previa
- Chat entre vecinos (foro)
- Multi-distrito (Usera ≠ Villaverde como dos mesas)
5. Explícitamente fuera (no backlog disfrazado)
| Ítem | Por qué |
|---|---|
| 112 como canal | PREGÓN no es 112 |
| Cita / slot / «te llamamos» | PREGÓN no cita |
| Expediente SIA | Mensaje ≠ expediente |
| Cuaderno de fórmulas / Hoy bento | ≠ MATIZ |
| Wizard + comparador de fincas | ≠ ZAGUÁN |
| Lookbook editorial | ≠ MARGEN |
| App nativa | Fuera de stack daily |
| Hero 2-col + 3 cards | AP-12 |
6. Orden de ataque si hay continuidad de código
- Contratos 05–06 + seed 05 + smoke 13 (alinear leftover MATIZ)
- Web paridad UI-00…11
- Acuse público mínimo + rate limit (L+1)
- Foto POST si hay campo
- Evaluar brief L3 (no parche silencioso)
16-interaction-specs.md
16 — Especificación de interacción — PREGÓN
1. Convenciones
| Token | Valor |
|---|---|
| Duración corta | 120–160ms |
| Easing | ease-out |
| Reduced motion | 0ms |
| Target | ≥44×44 |
| Pending | control disabled + aria-busy |
Loading (skeleton de UI-10) y mesa mobile (UI-11) viven en este doc junto a los gestos; el inventario Paper ya reserva UI-10 y UI-11. [DECISIÓN]
2. Chat home (UI-01) + mobile (UI-02) · H-CHAT
| Gesto | Resultado |
|---|---|
| Primera carga | Loading: 3 bloques bando + barra composer; aria-busy |
| 200 con 3 | hilo + compositor. Lead: eslogan. 3 datos: los 3 bandos |
| 200 con 0 | UI-08 empty · compositor sigue |
| Error 5xx / red | UI-09 + Reintentar (repite GET bandos) |
| Scroll hilo | Composer fijo en 390; no hide-on-scroll v1 |
| Tap Mesa (ghost) | /login o /mesa si hay token |
| Hover bando desktop | Nada (no es link). Sin lift |
AC: título y cuerpo de cada bando visibles sin hover. 3 bandos en el fold a 390 (AP-21). Cero botones Cita / Crear cuenta / 112-como-envío.
El aviso «PREGÓN no es 112» es texto, no un tel:112 primario. Could: link textual tel:112 secundario junto al disclaimer. [DECISIÓN v1] texto sin tel: para no competir con Enviar.
3. Composer (UI-03)
| Campo | Interacción |
|---|---|
| authorName | text · autocomplete name |
| street | text |
| kind | 5 chips radio |
| body | textarea 3+ filas |
| Gesto | Resultado |
|---|---|
| Chip tipo | aria-pressed único |
| Enviar | POST; pending; no navegar si inválido |
| Submit inválido | Foco al primer campo error; no POST |
| Error red | Botón Reintentar; no limpia campos |
| Doble tap Enviar | Ignorado mientras pending |
| 201 | Navega /ok/:code con state (calle, tipo, code) |
Submit deshabilitado si nombre < 2, calle < 4, kind vacío o body < 10.
[DECISIÓN] Un solo botón primario: Enviar. No existe «Enviar y resolver» ni «Citar».
390: CTA full-width h-44 en el composer anclado. Teclado iOS no debe tapar el textarea: scroll-margin en campos. Safe-area inferior.
Desktop 1440: composer en columna o bajo el hilo, CTA min 200px no full-bleed de viewport.
4. Success (UI-04)
| Gesto | Resultado |
|---|---|
| CTA primario | Escribir otro / |
| Copy | Visible «Enviado no es resuelto» + código + «no es 112» |
| Recarga | Código de URL + copy; sin hidratar GET JWT |
Sin confetti. Sin farola que se enciende. Sin «cobrar». Sin «te llamamos».
5. Login (UI-05)
| Gesto | Resultado |
|---|---|
| Submit | POST login; pending en botón |
| 200 | Guarda token + user; navega next sanitizado o /mesa |
| 401 | Texto bajo el form; password no se limpia |
| Enter | Submit |
| Volver al chat | / |
| Primera carga | Form visible; no skeleton de marca |
next permitido: paths que empiezan por / y no // ni http. Allowlist: /mesa, /mesa/PG-XXXX.
Login no muestra rail.
6. Kanban (UI-06) · F-KAN
| Gesto | Resultado |
|---|---|
| Primera carga | Loading: 3 columnas × 2 cards; aria-busy |
| 200 | cards en Recibido / En curso / Cerrado |
| Columna 0 | UI-08 de esa columna |
| Error | UI-09 + Reintentar |
| Tap card | /mesa/:code (card completa clicable) |
| Hover desktop | Rule 1px ink; no elevación |
| Drag-and-drop | No Must v1 |
| Tap rail Recibido | Scroll/foco a columna 1 (desktop); ?col=recibido Should |
| Pull-to-refresh | No en v1 |
AC: código, tipo y calle visibles en cada card sin hover. Cero botones Citar / 112 / Expediente.
7. Ficha (UI-07)
| Gesto | Resultado |
|---|---|
| Tap ← Mesa | /mesa |
| Chip status | PATCH { status }; pending en el grupo; aria-pressed único |
| 200 PATCH | Chip actualiza; no navega fuera |
| Error PATCH | Banner; status visual no miente (revierte chip) |
| Foto | No lightbox v1 |
| Scroll cuerpo | Lectura normal; sin scroll-jacking |
Sin «llamar ahora». Sin WhatsApp. Sin «citar». Sin «abrir expediente».
8. Empty (UI-08) · Error (UI-09) · Loading (UI-10)
| Estado | Interacción |
|---|---|
| Empty chat | Titular + 1 línea. Composer visible. Sin CTA de sede |
| Empty columna | Titular + 1 línea |
| Error | Banner + Reintentar; foco al banner; no pierde form |
| Loading chat | 3 bloques + barra; no spinner centrado de marca |
| Loading mesa | 3 × 2 cards fog/card |
| Loading ficha | 1 foto 4:3 + 4 barras |
9. Mesa mobile (UI-11)
| Gesto | Resultado |
|---|---|
| Tap segmento | Cambia columna; lista de esa columna |
| Tap fila | /mesa/:code |
| Swipe horizontal entre columnas | No Must (S2: Rosa no, y Nuria en 390 usa segmentos) |
| Rail | No se pinta; segmentos ocupan su sitio |
390: una columna a la vez. [DECISIÓN]
10. Rail (S-SIDE) desktop
| Gesto | Resultado |
|---|---|
| Tap Recibido / En curso / Cerrado | Foco de columna; aria-current |
| Tap Salir | Limpia storage → /login |
| Tap wordmark (auth) | /mesa |
| Scroll tablero | Rail fijo; no hide-on-scroll v1 |
11. Motion
| Superficie | Spec |
|---|---|
| Cambio de ruta | Fade 160ms (off si reduced-motion) |
| Bandos | Fade 160ms, stagger ≤30ms (off si reduced-motion) |
| Chip status | Crossfade 120ms |
| Rail | Sin hide-on-scroll v1 |
| Skeletons | Estáticos o pulso 1.2s opacity 0.6–1; off si reduced-motion |
Prohibido: scroll-jacking, page transitions de 400ms, parallax de plaza, sirena, farola que se enciende al enviar, confetti de «resuelto».
12. Criterios de aceptación de interacción
- Una vecina en 390px llega de abrir
/a ver un código en ≤ 3 taps útiles (tipo → campos → Enviar). - Ningún submit permite doble POST.
- El composer no cubre el H1 ni el último bando (spacer).
- Reduced-motion elimina stagger y slide.
- La home no es hero 2-col (AP-12).
- En 390 los 3 bandos son visibles sin scroll de «marca» (AP-21) — o 2 + compositor si el teclado no está abierto; con teclado cerrado, 3.
/no presenta ningún control de cuenta, cita o expediente.- El loading de chat es skeleton de hilo, no un logo municipal.
- El acuse muestra «enviado ≠ resuelto» y «no es 112» sin scroll en desktop.
- Nuria en 1440 mueve un parte a En curso en ≤ 3 taps (card → chip).
- Nuria en 390 usa lista por columna, no 3 columnas de 120px.
- Manos en el banco: cero gestos que exijan hover o swipe horizontal como único camino.
17-prototype-map.md
17 — Mapa de prototipo — PREGÓN
Paper no es clicable vía MCP. El prototipo vivo es la app Angular.
Los artboards de Paper se construyen en paralelo; esta tabla es el contrato de paridad.
| Ruta | Paper | Rol |
|---|---|---|
/ | UI-01, UI-02, UI-03, UI-08, UI-09, UI-10 | VECINA / Guest |
/ok/:code | UI-04 | VECINA / Guest |
/login | UI-05 | OPERADOR |
/mesa | UI-06, UI-08, UI-09, UI-10 | OPERADOR |
/mesa/:code | UI-07 | OPERADOR |
/mesa ~390 | UI-11 | OPERADOR |
Proceso: UX-00…UX-11. Tokens: UI-00.
Empty, error y loading se prototipan como estados / variantes de UI-01 / UI-06 además de sus boards propios. Ver doc 16.
Recorrido demo (guión)
- Abrir
/sin token — chat público. Leer eslogan Escríbele al distrito. Leer 3 bandos (agua, enseres, no es 112). - Composer: nombre Rosa Méndez, calle Plaza Elíptica, banco de la plaza, tipo Farola, texto La farola del banco no enciende desde anoche.
- Enviar →
/ok/PG-XXXX— «Enviado no es resuelto. El acuse no apaga la farola.» «PREGÓN no es 112.» - Ghost Mesa →
/login. Entrarnuria@pregon.ayto/password123. /mesa— Recibido 3 (PG-2401 farola Rosa · PG-2402 cubo Andrés · PG-2403 acera Rosa) · En curso 2 · Cerrado 2.- Tocar PG-2401 — ficha, foto farola, cuerpo, «El mensaje no es un expediente. PREGÓN no cita.»
- Chip En curso → PATCH. Volver a mesa: Recibido 2 / En curso 3.
- Comprobar que no hay botón Citar, 112-como-envío ni Expediente.
- Viewport 390: chat con composer anclado; mesa con segmentos + lista.
- Salir →
/login./sigue siendo el chat.
Estados en el mismo prototipo
| Estado | Cómo provocarlo |
|---|---|
| Empty chat | 0 filas en bandos (entorno de test) |
| Empty columna | 0 reports de ese status |
| Error | API caída / 5xx |
| Loading | throttle de red en GET /bandos o /reports |
| 404 parte | /mesa/PG-9999 con token |
| 401 mesa | /mesa sin token → login |
| Chat 390 | 390 × 844 en / |
| Chat 1440 | 1440 × 900 / (UI-01) |
| Mesa 390 | 390 × 844 /mesa (UI-11) |
| Acuse recargado | F5 en /ok/PG-2408 · código + copy, sin ficha |
Fuera del prototipo clicable
- Paper artboards son estáticos.
- Brigada de alumbrado / limpieza (fuera).
- 112 / 010 / sede electrónica.
- Cita con el distrito (no existe).
- GIS / mapa.
- Cuenta de vecino.
- Foro de partes ajenos.
- El leftover MATIZ (
/fichas,/hoy) no forma parte del prototipo PREGÓN: si aparece, es desalineación (I01).
18-completeness-audit.md
18 — Auditoría de completitud — PREGÓN (2026-08-20)
1. Alcance auditado
Vertical slice L1: chat-first de incidencias vecinas + mesa OPERADOR JWT (kanban Recibido / En curso / Cerrado), con docs, referencia Paper, contratos Angular + Nest + Neon.
Este encargo cubre la suite documental + Paper + vertical slice. El day brief y el paper-reference no se han sobrescrito.
2. Checklist CRON / ALS-2
| Requisito | Estado | Evidencia |
|---|---|---|
| Diversidad sector/tipo/nivel | OK | Admin pública L1; no belleza; no inmobiliaria; no galería; no L2 consecutivo |
| Terna S/H/F 0 códigos = N−1 · ≥2 vs N−2 | OK | S-SIDE · H-CHAT · F-KAN vs MATIZ S-BOTTOM · H-BENTO · F-EDIT |
| 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 01M0EZQ31KF04PAQ5RMCV7DQ2F |
| Docs 00–20 | OK | suite en docs/ + executive + README |
| JWT OPERADOR | OK | Role OPERADOR · D-P1-05 · mesa cerrada, chat público |
| API + seed + Neon | OK | contratos empty-surf-15969880, port 3020, health pregon-api |
| Web tokens | OK | Ibarra Real Nova + Jost · paleta highlighter |
| Chat-first (no AP-12 / AP-21) | OK | UI-01 / docs 03, 07, 08 |
| Hipótesis no fake field | OK | etiquetas en doc 02 |
| Copy ×4 irrenunciable | OK | docs 01, 09, 16 |
| Ground G-FOG | OK | #F1F3F6 · no G-CHAPEL / G-CONCRETE / G-GALLERY |
3. Cobertura funcional
| Feature brief | Spec | UI Paper | API | Docs |
|---|---|---|---|---|
| Chat bandos + compositor | Sí | UI-01…03 | GET bandos · POST reports | 03, 04, 16 |
| Acuse código | Sí | UI-04 | POST 201 | 04, 09 |
| Login OPERADOR | Sí | UI-05 | POST login | 04, 06 |
| Kanban 3 columnas | Sí | UI-06 | GET reports | 03, 04, 16 |
| Ficha + mover columna | Sí | UI-07 | GET/PATCH :code | 05, 16 |
| Empty | Sí | UI-08 | 200 [] | 16 |
| Error | Sí | UI-09 | 5xx/red | 16 |
| Loading | Sí | UI-10 | pending | 16, 17 |
| Mesa mobile | Sí | UI-11 | misma API | 04, 16 |
| Design system | Sí | UI-00 | — | 08 |
| Seed 1 + 3 + 7 (3/2/2) | Sí | — | seed | 05 |
| Health pregon-api | Sí | — | GET health | 05, 06, 20 |
4. Cobertura Paper (literales)
| ID | Nombre | En 00-paper-reference |
|---|---|---|
| UX-00 | Cover | Sí |
| UX-01 | Stakeholders | Sí |
| UX-02 | Personas | Sí |
| UX-03 | JTBD | Sí |
| UX-04 | Stories | Sí |
| UX-05 | Journey | Sí |
| UX-06 | Blueprint | Sí |
| UX-07 | Site map | Sí |
| UX-08 | Flujos | Sí |
| UX-09 | Datos+permisos | Sí |
| UX-10 | Métricas | Sí |
| UX-11 | Research | Sí |
| UI-00 | Tokens | Sí |
| UI-01 | Chat home | Sí |
| UI-02 | Chat mobile | Sí |
| UI-03 | Composer | Sí |
| UI-04 | Success | Sí |
| UI-05 | Login | Sí |
| UI-06 | Kanban | Sí |
| UI-07 | Ficha | Sí |
| UI-08 | Empty | Sí |
| UI-09 | Error | Sí |
| UI-10 | Loading | Sí |
| UI-11 | Mesa mobile | Sí |
[COMPROBADO] UX-count: 12 y UI-count: 12 literales. get_basic_info = 12 UX + 12 UI + 4 labels §; gate check-paper-reference.mjs exit 0.
5. Rúbrica de calidad (auto SCORE orientativo · docs)
| Eje | Score 1–5 | Comentario |
|---|---|---|
| Diversidad | 5 | Chat+kanban admin vs PWA oficio MATIZ, wizard ZAGUÁN y galería MARGEN; L1 no L2 |
| Craft visual (spec) | 4–5 | Highlighter + Ibarra/Jost + fotos de parte documentadas |
| Densidad UX docs | 5 | Suite L1: tablas, AC, etiquetas S/H/D |
| Completitud código L1 | 5 | Smoke :3020 health/bandos/login/GET+POST+PATCH reports + ng build; leftover MATIZ droppeado |
| Authz | 5 | Contrato: chat público; mesa 401; POST reports 201 |
| Verdad investigación | 5 | Sin entrevistas ni stats de campo falsas |
6. Huecos aceptados (no regresiones de cierre documental)
| Hueco | Clasificación |
|---|---|
| GIS / 112 / cita / expediente / cuenta vecina | Fuera L1 |
| e2e automatizado | L+1 |
| Foto en POST | L+1 |
| Acuse público hidratado | L+1 / D9 v1 |
Leftover clients / formulas / hoy / matiz-api / :3019 | Build; contrato de esta suite gana |
| Paper/app no necesariamente tocados aquí | Encargo = documentación |
7. Veredicto
COMPLETO para entrega documental del caso 2026-08-20.
La suite define producto L1 usable (tablas, AC, riesgos, contratos) sin presentar investigación de campo como hecho.
Cualquier ampliación GIS / 112 / sede / cuenta-vecino / cita requiere nuevo brief de diversidad, no parche silencioso.
19-backlog-completo.md
19 — Backlog completo — PREGÓN
Inventario de ítems. Los del alcance L1 documental del día están Done.
El resto es opcional / siguiente nivel, no deuda oculta del cierre.
1. Done — L1 case documental (2026-08-20)
| ID | Ítem | Capa |
|---|---|---|
| D01 | Definición producto PREGÓN + eslogan | Docs |
| D02 | Day brief diversidad admin pública L1 | Docs (preexistente, no pisado) |
| D03 | Terna R-IN · S-SIDE · H-CHAT · F-KAN | Docs |
| D04 | Personas Rosa Méndez / Nuria Delgado / Andrés Peña | Docs + Paper ref |
| D05 | JTBD + stories Must | Docs |
| D06 | IA chat-first + chrome S-SIDE | Docs |
| D07 | Flujos F0–F9 + AC | Docs |
| D08 | Modelo users / bandos / reports | Docs |
| D09 | Contratos API :3020 · health pregon-api | Docs |
| D10 | Auth JWT OPERADOR · POST reports público | Docs |
| D11 | Status RECIBIDO | CURSO | CERRADO · 401 mesa | Docs |
| D12 | Composer calle + tipo + texto + nombre | Docs |
| D13 | Microcopy ×4 (expediente / resuelto / cita / 112) | Docs |
| D14 | North star partes a En curso en 24 h | Docs |
| D15 | Tokens highlighter + Ibarra Real Nova / Jost | Docs |
| D16 | Paper UX-00…11 + UI-00…11 | Docs |
| D17 | Suite docs 00–20 + README + executive | Docs |
| D18 | Seed 1 OPERADOR + 3 bandos + 7 partes 3/2/2 | Docs |
| D19 | Assets listados (hero + 4 partes) | README |
| D20 | QA smoke + S1–S27 | Docs |
| D21 | Neon empty-surf-15969880 | Docs |
| D22 | Chat 390/1440 + mesa rail/lista especificados | Docs |
| D23 | Ground G-FOG #F1F3F6 | Docs |
| D24 | Rutas reales solo las 5 del encargo | Docs |
Los must-have del brief (chat, acuse, login, kanban, ficha, empty/error/loading, mesa mobile, ≥12 UX + ≥10 UI) están dentro de D01–D24, no como leftover.
2. Backlog L1 implementación (repo pregon-app)
Alineación de código al contrato ya cerrado. No es alcance documental pendiente.
| ID | Ítem | Prioridad | Notas |
|---|---|---|---|
| I01 | Alinear apps/api + apps/web al contrato PREGÓN | P0 | quitar leftover MATIZ clients/formulas/hoy |
| I02 | Schema users / bandos / reports | P0 | |
| I03 | Seed Nuria + 3 bandos + 7 reports 3/2/2 | P0 | |
| I04 | GET bandos público · GET reports JWT agrupado | P0 | 401 sin token en reports |
| I05 | POST reports público + PATCH :code JWT | P0 | status RECIBIDO al crear |
| I06 | POST login JWT | P0 | nuria@pregon.ayto |
| I07 | GET health service: "pregon-api" | P0 | no matiz-api |
| I08 | Auth guard Angular solo /mesa y /mesa/:code | P0 | / y /ok/:code públicos |
| I09 | Chat / ok / login / mesa / parte | P0 | |
| I10 | Tokens Tailwind fog/ink/mark + fotos + rail | P0 | Ibarra + Jost |
| I11 | Smoke curl + build | P0 | D-P1-06 |
| I12 | Puerto API 3020 · proxy y environment | P0 | no 3019 |
3. Backlog L+1 (polish)
| ID | Ítem | Prioridad | Notas |
|---|---|---|---|
| B01 | Acuse público mínimo { code, status } | P1 | no listar body |
| B02 | Foto en POST allowlist | P2 | |
| B03 | Rate limit login + POST reports | P1 | anti stuffing / spam |
| B04 | Playwright smoke | P1 | CI |
| B05 | Skeleton UI-10 en código | P2 | |
| B06 | GET /api/me | P2 | |
| B07 | Borrador local del composer | P3 | no es enviado |
| B08 | API URL por environment | P1 | deploy |
| B09 | Página 404 amigable | P3 | |
| B10 | Sessions invalidables | P2 | |
| B11 | Drag-and-drop columnas | P2 | chips siguen siendo Must |
| B12 | Nota interna OPERADOR | P3 | no chat al vecino |
4. Backlog L3 (requiere brief nuevo)
| ID | Ítem | Dependencia |
|---|---|---|
| C01 | Multi-operador / turnos | users + membership |
| C02 | Cuenta VECINA + «mis partes» | authz · rompe H3 |
| C03 | GIS / mapa | S4 se rompe · brief nuevo |
| C04 | Expediente SIA / sede / Cl@ve | microcopy se rompe |
| C05 | Integración 112 / 010 | PREGÓN no es 112 se rompe |
| C06 | Cita previa | PREGÓN no cita se rompe |
| C07 | Foro vecinal / partes ajenos | D14 se rompe |
| C08 | Dos mesas Usera ≠ Villaverde | S1 se relaja |
5. Backlog explícitamente fuera
| ID | Ítem |
|---|---|
| E01 | 112 como canal de producto |
| E02 | Cita / slot / «te llamamos a las 10» |
| E03 | Cuaderno de fórmulas / Hoy bento |
| E04 | Wizard + comparador de fincas |
| E05 | Lookbook editorial / agenda de crónicas |
| E06 | App nativa |
| E07 | Hero 2-col + 3 cards de marca |
6. Explicitamente no-backlog
| Idea | Razón |
|---|---|
| Hero 2-col + 3 cards | AP-12 / rompe H-CHAT |
| Bottom tabs Hoy · Fichas · Cuenta | S-BOTTOM de MATIZ |
| Chrome de pasos 1—2—3 | S-WIZ de ZAGUÁN |
| Masthead de periódico | S-TOP de MARGEN |
| Form «pide cita» | PREGÓN no cita |
| «Ya está resuelto» al enviar | Enviado ≠ resuelto |
Health matiz-api | Contrato pregon-api |
7. Orden de ataque recomendado (continuidad de código)
- I01–I12 (vertical slice runnable, sin leftover MATIZ)
- B03 + B04 + B08
- B01 + B06
- B02 + B11 si hay campo
- Evaluar brief L3 — no parche silencioso
8. Trazabilidad
| Origen | Ítems |
|---|---|
| Day brief must-have | D01–D24, I01–I12 |
| Doc 15 L+1 | B01–B12 |
| Doc 15 L3 / fuera | C01–C08, E01–E07 |
20-implementation.md
20 — Implementación — PREGÓN
1. Resumen ejecutivo técnico
| Campo | Valor |
|---|---|
| App path | /Users/cristian/orca/pregon-app |
| API | NestJS · puerto 3020 · prefijo /api |
| Web | Angular standalone · puerto 4200 |
| Package | apps independientes (npm --prefix o pnpm --filter) |
| DB | Neon PostgreSQL · project empty-surf-15969880 |
| Tablas | users, bandos, reports (+ sessions Should) |
| Auth | JWT Bearer · role OPERADOR (mesa); chat y POST reports públicos |
| Dominio | User, Bando, Report |
| Fecha | 2026-08-20 |
| GitHub | https://github.com/Criscode2022/pregon-app |
Este documento es la especificación de build alineada al case. No sustituye al código: si el repo diverge, gana el contrato de docs 05 + este archivo tras actualizar ambos.
[COMPROBADO] Path, puerto, Neon, Paper y GitHub salen del encargo.
[COMPROBADO] El repo /Users/cristian/orca/pregon-app está alineado: health pregon-api, puerto 3020, módulos bandos/reports, rol OPERADOR, tokens highlighter, rutas / /ok/:code /login /mesa. Tablas leftover MATIZ se dropean al boot.
2. Cómo arrancar
cd /Users/cristian/orca/pregon-app
# instalar dependencias por app (no npm workspaces)
# apps/api/.env
# DATABASE_URL= # Neon empty-surf-15969880
# JWT_SECRET=
# PORT=3020
# schema + seed
# api → http://localhost:3020
# web → http://localhost:4200
Credenciales
| Rol | Password | |
|---|---|---|
| OPERADOR | nuria@pregon.ayto | password123 |
Vecina / vecino: sin cuenta. Escriben en /.
3. Schema
Al boot o vía migrate:
CREATE TABLE/ migrate deusers,bandos,reports(doc 05). Opcionalsessions.- Índices:
reports(code),reports(status, created_at DESC),users(email),bandos(pinned, created_at). - Seed idempotente:
- upsert OPERADOR
nuria@pregon.ayto(bcrypt depassword123) - upsert
bandosporid(bnd_1…bnd_3) - upsert
reportsporcode(PG-2401…PG-2407)
- upsert OPERADOR
- Leftover: no recrear
clients/formulas/sittings. Drop si existen y no hay datos ajenos que preservar. [DECISIÓN]
No borrar partes nuevos de Rosa en cada boot si ya existen filas distintas al seed.
Generador de código: SELECT max(code) numérico + 1, formateado PG- + 4 dígitos (padding). Seed máximo 2407 → siguiente POST = PG-2408. Si se agota 9999, L+1 amplia a 5 dígitos; no es Must.
Tablas (recordatorio)
| Tabla | Contenido |
|---|---|
users | OPERADOR Nuria |
bandos | 3 anclados |
reports | 7 partes 3/2/2 |
sessions | Should: sid + token_hash + expires_at |
4. Módulos API a implementar
Auth
POST /api/auth/login- Valida email/password; compara bcrypt; emite JWT con
sub, email, role. - Guard JWT +
role === OPERADORprotege GET/PATCH reports. - Públicos: health, bandos, POST reports, login.
Oficio
| Método | Ruta | Auth | Notas |
|---|---|---|---|
| GET | /api/health | No | { ok: true, service: "pregon-api" } |
| GET | /api/bandos | No | pinned true, created_at asc |
| POST | /api/reports | No | crea RECIBIDO + code |
| GET | /api/reports | JWT | agrupado default; ?view=flat; ?status= |
| GET | /api/reports/:code | JWT | |
| PATCH | /api/reports/:code | JWT | { status } · no street/body |
Health (referencia de implementación)
Sustituir leftover:
HealthService.name = 'pregon-api' // no 'matiz-api'
main.ts PORT default 3020 // no 3019
Reports agrupados (referencia)
recibido = WHERE status = 'RECIBIDO' ORDER BY created_at DESC
curso = WHERE status = 'CURSO' ORDER BY created_at DESC
cerrado = WHERE status = 'CERRADO' ORDER BY created_at DESC
return { recibido, curso, cerrado }
JSON camelCase: authorName, createdAt, districtName.
SQL: author_name, created_at, district_name.
POST no toca bandos ni genera cita ni llama al 112.
Quitar módulos leftover clients/, formulas/, hoy/ y cualquier seed COLORISTA.
5. Frontend a implementar
| Página | Ruta | Responsabilidad |
|---|---|---|
| ChatPage | / | GET bandos, composer, POST reports, empty/loading/error |
| OkPage | /ok/:code | código + copy; state opcional; sin GET JWT |
| LoginPage | /login | form → login → mesa |
| MesaPage | /mesa | GET reports, kanban / lista 390, empty/loading/error |
| PartePage | /mesa/:code | GET :code, chips PATCH |
Shell autenticada: SideRailComponent (desktop) / segmentos (390). Chat y ok: sin rail. Login: sin rail.
ApiService centraliza base URL http://localhost:3020/api, token storage (pregon_token, pregon_user), métodos tipados (Bando, Report, ReportsGrouped, User).
AuthGuard solo en /mesa y /mesa/:code. Interceptor Authorization: Bearer cuando hay token (no bloquea POST público).
index.html: título PREGÓN — Escríbele al distrito. Fuentes Ibarra Real Nova + Jost. Body bg-fog text-ink.
proxy.conf.json y environment*.ts: 3020, no 3019.
Rutas leftover /fichas, /hoy, /cuenta, /fichas/:slug/formula fuera.
6. Decisiones de implementación
| Decisión | Razón |
|---|---|
| Puerto API 3020 | Evitar colisión con MATIZ 3019 |
Tablas bandos / reports | Dominio de pregón, no leftover clients |
Role OPERADOR | Encargo; no COLORISTA |
Health pregon-api | Encargo; no matiz-api |
| Chat público + mesa JWT | S2 / H3 / D-P1-05 |
| JSON camelCase | Convenio web serie daily |
PATCH :code solo status | Contrato del encargo |
Success /ok/:code sin GET | D9 enumeración |
| Cover allowlist | Evitar URL arbitraria |
| Apps independientes | AP-06 |
| Home no usa hero 2-col | AP-12 / H-CHAT |
| Kind enum cerrado | S4 |
| Composer sin foto Must | Encargo POST 4 campos |
| Mesa 390 = lista | UI-11 |
7. Variables de entorno
| Variable | Servicio | Descripción |
|---|---|---|
DATABASE_URL | API | Neon empty-surf-15969880 |
JWT_SECRET | API | Firma tokens |
PORT | API | opcional, 3020 |
JWT_EXPIRES | API | opcional, default 12h |
Web: URL de API en ApiService (default localhost:3020).
8. Smoke de implementación (mínimo)
- Schema OK (tablas
users,bandos,reportsexisten). GET /api/health200{ ok: true, service: "pregon-api" }.GET /api/bandos200 · 3 bandos.POST /api/auth/login200 +accessToken+role=OPERADOR.GET /api/reportsBearer: agrupado 3/2/2, codes PG-2401…2407.GET /api/reportssin token 401.POST /api/reportssin token 201status=RECIBIDO.PATCH /api/reports/PG-2401{ "status": "CURSO" }Bearer 200.- Web: hilo dominante, rail en mesa, disclaimers ×4, fotos 4:3, fuentes Ibarra/Jost.
9. Estructura de ficheros clave (objetivo)
pregon-app/
├── package.json
├── apps/api/
│ ├── src/
│ │ ├── main.ts # PORT 3020
│ │ ├── app.module.ts
│ │ ├── auth/
│ │ ├── bandos/
│ │ ├── reports/
│ │ └── health.controller.ts # service: pregon-api
│ └── .env.example
├── apps/web/
│ ├── proxy.conf.json # :3020
│ ├── tailwind.config.js # fog / ink / mark / …
│ └── src/app/
│ ├── app.routes.ts
│ ├── core/api.service.ts
│ ├── shell/side-rail.component.ts
│ └── pages/
│ ├── chat/
│ ├── ok/
│ ├── login/
│ ├── mesa/
│ └── parte/
└── README.md
Nombres de fichero orientativos; el contrato es de rutas y tablas, no de filenames exactos.
Quitar módulos leftover clients/, formulas/, hoy/ y páginas hoy/, fichas/, ficha/, formula/, cuenta/.
10. Seed — checklist de implementación
| # | Check |
|---|---|
| 1 | User Nuria Delgado / nuria@pregon.ayto / password bcrypt de password123 / role OPERADOR |
| 2 | 3 bandos bnd_1 bnd_2 bnd_3 pinned |
| 3 | 7 reports PG-2401…2407 con kinds y covers de doc 05 |
| 4 | Conteos 3 RECIBIDO · 2 CURSO · 2 CERRADO |
| 5 | Rosa autora de 2401, 2403, 2405, 2407 |
| 6 | Andrés autor de 2402, 2404, 2406 |
| 7 | Idempotencia: upsert por email / bando id / report code |
11. Alineación case ↔ app
| Doc | Evidencia esperada en código |
|---|---|
| 05 data model | columnas y enums |
| 03–04 IA/flows | routes + controllers |
| 08 DS | tailwind colors + fontFamily Ibarra/Jost |
| 09 content | strings en templates (microcopy ×4) |
| 00 paper | URLs en README case |
| 11 security | Guard en mesa; POST reports público; 401 GET reports |
12. Inventario de leftover (estado 2026-08-20, cierre)
El scaffold partió de MATIZ. I01 cerrado al reescribir dominio:
| Pieza | Estado al cierre |
|---|---|
Health pregon-api :3020 | [COMPROBADO] |
Módulos bandos + reports | [COMPROBADO] |
| Role OPERADOR JWT | [COMPROBADO] |
Rutas / /ok/:code /login /mesa | [COMPROBADO] |
| Tailwind fog/ink/mark + Ibarra/Jost | [COMPROBADO] |
Drop clients/formulas/sittings/listings/pieces | [COMPROBADO] en ensureSchema |
Smoke: GET /api/health, GET /api/bandos, POST /api/reports, login nuria@pregon.ayto, GET/PATCH /api/reports con JWT, ng build.
13. Notas de cierre técnico
- El case L1 está cerrado: docs + Paper 12 UX / 12 UI + vertical slice runnable.
- Must-have del brief tienen AC y smoke.
- Mejoras (acuse hidratado, e2e, foto POST) viven en backlog L+1.