On this page 00 — Day brief · 2026-08-20 · PREGÓN 0%

00-day-brief.md

00 — Day brief · 2026-08-20 · PREGÓN

Decisiones ALS-2 BRIEF

CampoValor
Fecha2026-08-20
NombrePREGÓN
ComplejidadNivel 1 (no L2 consecutivo tras MATIZ)
SectorAdministración pública local (distrito Usera / Villaverde)
TipoChat-first de incidencias vecinas + mesa OPERADOR JWT (kanban Recibido / En curso / Cerrado)
PlataformaWeb responsive (chat vecino · mesa desktop)
RegistroR-IN — el ayuntamiento se escribe, no se “tramita un formulario”
ShellS-SIDE — rail de estados en mesa; el chat público no lleva top-nav de marketing
HomeH-CHAT — la entrada es el hilo, no un hero
FlujoF-KAN — el operador mueve columnas; el vecino no ve el tablero
Por qué no MATIZNo S-BOTTOM / H-BENTO / F-EDIT; no belleza; no PWA de oficio
Por qué no ZAGUÁNNo S-WIZ / H-WIZARD / F-CMP; no inmobiliaria
Por qué no PIZARRAChat ≠ 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:
    • #F1F3F6 fotocopia (ground)
    • #12141A tinta de sello
    • #C8F000 rotulador fluorescente
    • #FFFFFF ficha
    • #5C6370 grafito
    • #C1121F lacre (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

  1. Home chat: hilo de bandos del distrito + compositor de incidencia (calle, tipo, texto)
  2. Acuse con código (enviado ≠ resuelto)
  3. Login JWT OPERADOR
  4. Mesa kanban Recibido / En curso / Cerrado
  5. Ficha de parte + cambio de columna
  6. Empty / error / loading / mobile chat
  7. ≥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

IDSeñal
H1Un chat reduce “no sé qué formulario es”
H2Separar enviado de resuelto evita la expectativa 112
H3Kanban de 3 columnas basta; el vecino no necesita login

00-paper-reference.md

Paper reference · PREGÓN

URLhttps://app.paper.design/file/01M0EZQ31KF04PAQ5RMCV7DQ2F
File ID01M0EZQ31KF04PAQ5RMCV7DQ2F

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)

IDNombreContenido
UX-00CoverPREGÓN · «Escríbele al distrito.» · R-IN · S-SIDE · H-CHAT · F-KAN · L1 admin pública · highlighter · 2026-08-20
UX-01StakeholdersVECINO (Rosa), OPERADOR (Nuria), distrito, 112 fuera de alcance
UX-02PersonasRosa Méndez 63 · Nuria Delgado 41 · Andrés Peña 38 (vecino digital)
UX-03JTBDJob «decir que la farola no enciende sin adivinar el formulario»
UX-04StoriesMust: chat, envío, acuse, login, kanban, ficha, estados
UX-05JourneyLee bando → escribe parte → código → (Nuria) mesa → En curso → Cerrado
UX-06BlueprintFrontstage chat · backstage brigada · Nest/Neon/JWT
UX-07Site mapPúblico / chat · /ok/:code · /login · /mesa kanban
UX-08FlujosF-CHAT público; F-KAN OPERADOR
UX-09DatosUser OPERADOR · Report · Bando; JWT en mesa
UX-10MétricasNorth star % partes que Nuria mueve a En curso en 24 h
UX-11ResearchComprobado / supuesto / hipótesis / decisión

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

IDNombreFlujo
UI-00TokensDS highlighter: fog, ink, mark, card, graphite, seal
UI-01Chat homeH-CHAT 1440: bandos + hilo + composer. Lead + 3 datos
UI-02Chat mobile390: mismo hilo
UI-03ComposerTipos farola/cubo/banco/acera
UI-04SuccessCódigo. Enviado ≠ resuelto
UI-05LoginJWT OPERADOR
UI-06KanbanRecibido / En curso / Cerrado
UI-07FichaParte + foto + mover columna
UI-08EmptySin partes / chat vacío
UI-09ErrorRed
UI-10LoadingSkeletons
UI-11Mesa mobileLista por columna

Mapeo Paper → Angular

ArtboardRutaComponente
UI-01 Chat/ChatPage
UI-04 Success/ok/:codeOkPage
UI-05 Login/loginLoginPage
UI-06 Kanban/mesaMesaPage
UI-07 Ficha/mesa/:codePartePage

01-project-definition.md

01 — Definición de proyecto — PREGÓN

1. Identidad

CampoValor
NombrePREGÓN
SignificadoPregó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 fraseChat-first de incidencias vecinas + mesa OPERADOR JWT (kanban Recibido / En curso / Cerrado).
SectorAdministración pública local (distrito, no ministerio ni 112)
TipoWeb L1 — chat público + acuse + login + kanban + ficha — Nivel 1, producto usable compacto
PlataformaWeb responsive (chat vecino ~390 · mesa desktop con rail)
Mercado demoEspaña · Madrid · distrito Usera / Villaverde (una mesa, no el Ayuntamiento entero)
Idiomaes-ES
Fecha caso2026-08-20
TernaR-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

ProblemaQuién lo sufreEfecto
«No sé qué formulario es»VECINA (Rosa)Abandona; escribe a un grupo de WhatsApp; no llega a Nuria
Enviado se lee como resueltoVECINA + OPERADOR«Ya puse el parte» cuando la farola sigue apagada
El chat se lee como 112VECINA + distritoUrgencia vital en un canal de farolas
El parte se lee como expediente SIAOPERADOR + distritoEsperan número de procedimiento, plazos, sede
La mesa se lee como citaVECINAEsperan «te llamamos a las 10»
La dueña de la bandeja pide GIS el día 1DistritoEl 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

IDHipótesisSeñal de validación (futura)
H1Un chat reduce «no sé qué formulario es»% de visitas a / que terminan en POST con código
H2Separar enviado de resuelto evita la expectativa 112 / «ya está»↓ «pensé que ya estaba arreglado»; copy visible en acuse
H3Kanban de 3 columnas basta; el vecino no necesita login0 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

ParaValor
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 / VillaverdeUn pregón mínimo: bandos + partes + mesa, sin 112 y sin sede.

No es PREGÓN

ExcluidoPor qué
Expediente SIA / número de procedimientoMensaje ≠ expediente
«Ya está resuelto» al enviarEnviado ≠ resuelto
Cita / slot / «te llamamos a las 10»PREGÓN no cita
112 / urgencia vital / incendio / agresiónPREGÓN no es 112
Sede electrónica / Cl@ve / certificadoS3: el vecino no se identifica
GIS / mapa de farolasS4: calle en texto
Cuaderno de fórmulas / sillas de hoyEso fue MATIZ (H-BENTO / F-EDIT)
Wizard de criterios + comparador de pisosEso fue ZAGUÁN (H-WIZARD / F-CMP)
Lookbook / masthead editorialEso 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 GISL1 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

RolObjetivo medible en demo
VECINALeer un bando, escribir un parte y ver el código en < 60 s, sin cuenta
OPERADOREntrar a mesa, ver 3/2/2, abrir PG-2401 y pasarlo a En curso en < 45 s
VECINO digitalMismo 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ónAnónimo (vecino)OPERADORJUNTA / 112
Ver chat / bandosNo hay cuenta
POST parteSí (no es el job)No
Ver acuse /ok/:code (código en URL)
Ver mesa / fichaNo (401 / redirect)No
GET /api/reports401
PATCH status401
Abrir expediente SIANo (no existe)NoFuera
Llamar al 112Copy: fueraCopy: fueraEl 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)

TipoMétricaDefinición
North Star% de partes que Nuria mueve a En curso en 24 hOficio de mesa, no pageviews
Activación vecina1.er POST con codeH1
Integridad de expectativaReclamaciones «ya está resuelto» / «es el 112» / «¿dónde está mi cita?»H2 + microcopy
Integridad de authGET /api/reports sin token → 401; POST /api/reports sin token → 201Chat público + mesa cerrada
SaludError rate API reports / bandos4xx/5xx

7. Alcance funcional v1 (L1)

MóduloIncluido
ChatGET /api/bandos · hilo + compositor (calle, tipo, texto, nombre)
Parte públicoPOST /api/reports · { street, kind, body, authorName }PG-XXXX RECIBIDO
Acuse/ok/:code · «enviado ≠ resuelto»
AuthPOST /api/auth/login → JWT OPERADOR
MesaGET /api/reports JWT · kanban Recibido / En curso / Cerrado
FichaGET /api/reports/:code JWT · PATCH { status }
Estados UIEmpty (0 bandos / 0 partes), loading skeleton, error de red
MobileChat ~390px · mesa lista por columna · targets ≥44px
Seed1 OPERADOR, 3 bandos, 7 partes
SaludGET /api/health{ ok: true, service: "pregon-api" }

8. Criterios de aceptación de producto

  1. / es el chat público. No pide cuenta. No es un hero 2-col + 3 cards.
  2. Una vecina puede leer 3 bandos en el fold, escribir un parte (calle + tipo + texto + nombre) y obtener un código PG-XXXX.
  3. /ok/:code dice explícitamente que enviado no es resuelto y que PREGÓN no es 112.
  4. La UI dice explícitamente que mensaje no es expediente, que PREGÓN no cita y que PREGÓN no es 112.
  5. Sin token, GET /api/reports, GET /api/reports/:code y PATCH responden 401. POST /api/reports y GET /api/bandos son públicos.
  6. Un POST de parte crea status=RECIBIDO. Enviar no marca CERRADO.
  7. Una OPERADOR inicia sesión y ve el kanban con 3 RECIBIDO, 2 CURSO, 2 CERRADO en el seed.
  8. Una OPERADOR abre un parte y lo mueve de columna (RECIBIDO → CURSO → CERRADO).
  9. Fotos reales en las partes seed (assets partes/*).
  10. Tipos visibles: farola · cubo · banco · acera · otro.
  11. Rail S-SIDE de estados en mesa desktop. Chat público sin top-nav de marketing.
  12. Rutas solo las del doc 03. No existen /cita, /112, /sede, /expediente, /mapa.
  13. GET /api/health devuelve service: "pregon-api"no matiz-api.

9. Stack y artefactos

CapaDetalle
FrontendAngular + Tailwind · puerto 4200
BackendNestJS · puerto 3020
DBNeon PostgreSQL · project empty-surf-15969880
AuthJWT (OPERADOR)
DiseñoPaper 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

RiesgoImpactoMitigació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 112Urgencia vital en el chatCopy «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 1Alcance L3 disfrazadoCalle en texto; S4
Cuenta de vecinoRompe S2 / H3Sin signup; nombre libre en el parte
Home percibida como landing municipalPérdida de H-CHAT / AP-12El hilo es la home; no hero 2-col
Confundir con MATIZ (cuaderno de oficio)Expectativa de login-firstChat público; no Hoy bento
Confundir con ZAGUÁN (wizard)Expectativa de pasos 1—2—3Un compositor, no wizard
Leftover clients / formulas / hoy en repoApp enseña otro productoContrato de esta suite gana; I01 de build
Enumeración de partes por códigoPII de calle en públicoGET :code JWT; /ok/:code no hidrata ficha
0 bandosChat vacío sin oficioEmpty «Aún no hay bandos del distrito.»

11. Glosario

TérminoDefinición en PREGÓN
PregónAviso público del distrito; el producto, no un trámite
BandoMensaje anclado del distrito en el chat (bandos)
Parte / reportIncidencia vecina (reports); no es un expediente
MensajeLo que escribe Rosa en el compositor; no abre procedimiento SIA
ExpedienteProcedimiento administrativo; fuera. Mensaje ≠ expediente
EnviadoPOST 201 + código; no significa resuelto
ResueltoTrabajo de brigada en la calle; fuera de la app hasta CERRADO de mesa
CódigoPG-XXXX; acuse, no número SIA
RecibidoColumna 1 · RECIBIDO · acaba de entrar
En cursoColumna 2 · CURSO · Nuria lo está atendiendo
CerradoColumna 3 · CERRADO · la mesa lo da por hecho; no afirma la farola encendida
Tipo / kindfarola | cubo | banco | acera | otro
MesaSuperficie F-KAN de la OPERADOR
ChatSuperficie H-CHAT pública
OPERADORUsuario autenticado que opera la mesa
VECINA / VECINOAutora/autor del parte; no tiene fila en users
FogSuperficie de ground #F1F3F6
Ficha / cardSuperficie de card #FFFFFF
MarkRotulador fluorescente #C8F000
LacreAcento de sello / urgente #C1121F, poco
RailShell S-SIDE: estados de la mesa
112Emergencia vital; fuera

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

IDDecisiónAlternativa descartada
D1Home = chat (H-CHAT)Hero 2-col + 3 cards (AP-12) · bento (MATIZ) · wizard (ZAGUÁN) · galería (MARGEN)
D2Shell rail de estados en mesa (S-SIDE)Bottom tabs · masthead · wizard chrome · dock
D3Registro innovador highlighter (R-IN)Chalky / pizarra (primer instinto; eso fue PIZARRA) · chapel MATIZ
D4Flujo F-KAN (operadora mueve columnas)F-EDIT, F-CMP, F-STORY, F-LEAD, F-BOOK
D5Mood highlighterChalky (vetado como primer instinto), chapel MATIZ, rusted ZAGUÁN
D6Un rol autenticado OPERADORMulti-rol junta / brigada / vecino / 112
D7Estados de parte RECIBIDO | CURSO | CERRADO12 estados SIA; RESUELTO como eufemismo de enviado
D8Chat vecino sin cuentaSignup, Cl@ve, seguimiento con login
D9GET :code solo JWTTracking público enumerabile
D10POST público { street, kind, body, authorName }Formulario de 12 campos + adjuntos + DNI
D11Ground G-FOG #F1F3F6G-CHAPEL, G-CONCRETE, G-GALLERY, G-MOSS, G-CHROME
D12Success de acuse en /ok/:codeToast único sin pantalla
D13Kind = farola | cubo | banco | acera | otroTaxonomía de 40 tipos + GIS
D14Hilo público = bandos, no feed de partes ajenosForo vecinal (rompe privacidad de calle)
D15Chat 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

CaseTernaPor qué PREGÓN no lo copia
MATIZ (N−1)S-BOTTOM / H-BENTO / F-EDITNo belleza, no PWA de oficio, no editor de gramos, no auth-first total
ZAGUÁN (N−2)S-WIZ / H-WIZARD / F-CMPNo inmobiliaria, no wizard, no comparador
MARGEN (N−3)S-TOP / H-GALLERY / F-STORYNo editorial, no lookbook, no masthead
PIZARRAH-SEARCH / catálogoChat ≠ 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)

ObjetivoMétodo en este casoSalida
Entender actores del distritoModelado de stakeholders + personas§3–4
Definir job de «decir que la farola no enciende» y de mover columnasJTBD + stories Must§5
Mapear fricción sede / WhatsApp / 010 → chatJourney + service blueprint§6–7
Traducir a requisitos L1 chat + kanbanMatriz 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

ProhibidoPermitido
«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 realQuotes de persona etiquetadas como constructo de diseño
Volumen medido «14 partes/día en Villaverde» como KPI de mercadoPartes de seed de producto, etiquetadas como demo
Horario oficial de una junta concreta Usera-VillaverdeMesa 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

StakeholderInfluenciaInterésNecesidad principal
VECINA (Rosa)Alta en el parteMuy altaDecir lo de la farola sin cuenta ni formulario
OPERADOR (Nuria)AltaMuy altaVer lo recibido, mover a curso, cerrar
VECINO digital (Andrés)MediaAltaMismo chat, más rápido; no pide panel
Distrito / juntaAlta formal, baja en producto día 1AltaQue lleguen partes; que no se confunda con 112
Brigada de alumbrado / limpiezaMedia (fuera)AltaRecibe el trabajo fuera de PREGÓN
112 / SAMUR / PolicíaAlta en emergenciasFuera de alcance; no clonar el canal
010 / Linea Madrid / sedeCompetencia / canal paraleloNo clonar el expediente; sí sustituir «no sé qué formulario es»
Vecindario WhatsAppCanal paraleloNo 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

CampoDetalle
Edad / contexto63 años; vecina de Usera; poco hábito digital; escribe desde el banco de la plaza
DigitalMóvil con letra grande; no quiere «otra cuenta del ayuntamiento»; no usa Cl@ve
GoalsDecir que la farola no enciende; llevarse un código; no adivinar el formulario
PainsLa 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

CampoDetalle
Edad / contexto41 años; mesa del distrito Usera / Villaverde; turno de incidencias de calle
DigitalFluida en desktop; el móvil es secundario para la mesa
GoalsVer lo nuevo, abrir el parte, pasarlo a En curso, cerrar cuando la brigada avisa (fuera)
PainsBandeja 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 demonuria@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)

CampoDetalle
Edad / contexto38 años; Villaverde; usa el móvil todo el día
DigitalAlta; escribe rápido; no necesita onboarding
GoalsDejar el cubo apuntado en 20 s; guardar el código en el rollo de fotos
PainsFormularios 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)

NombreRol en seedPartes
Rosa MéndezP1PG-2401, PG-2403, PG-2405, PG-2407
Andrés PeñaP3PG-2402, PG-2404, PG-2406

[COMPROBADO] Nombres salen del encargo. Los textos de parte son catálogo demo.

Anti-personas

QuiénPor qué no es target v1
Jefa de sede electrónicaS3: no hay Cl@ve ni expediente
Vecina que quiere «cita con el concejal»PREGÓN no cita
Quién llama por un infartoPREGÓN no es 112
Community manager de smart cityS1: 8–20 partes/día; no es geoportal
Colorista de MATIZ que espera Hoy bentoOtro job, otro día
Buscador de ZAGUÁN que pregunta «tres fincas»Otro job, otro día
Operador que quiere 12 estados SIAH3: 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

JobRol
Distinguir enviado de resueltoVECINA / OPERADOR
Entender que no es el 112VECINA / distrito
Entender que no es un expedienteOPERADOR / distrito
No crear cuentaVECINA
Mover un parte de columnaOPERADOR
No ver el tablero desde la calleVECINA (H3)

Stories Must (v1)

IDStoryAC
US1Como vecina, quiero leer los bandos del distrito sin cuentaGET /api/bandos · 3 en el fold
US2Como vecina, quiero escribir calle, tipo y textoComposer UI-03
US3Como vecina, quiero un código al enviarPOST → PG-XXXX · /ok/:code
US4Como vecina, quiero que el acuse no diga «resuelto»Copy enviado ≠ resuelto
US5Como vecina, quiero que me digan que no es el 112Copy + bando bnd_3
US6Como operadora, quiero entrar con email/passwordJWT + redirect /mesa
US7Como operadora, quiero ver Recibido / En curso / CerradoGET /api/reports agrupado
US8Como operadora, quiero abrir un parteGET /api/reports/:code
US9Como operadora, quiero mover la columnaPATCH { status }
US10Como cualquiera sin token, no quiero ver la mesa401 / redirect login
US11Como vecina u operadora, quiero empty / error / loadingUI-08, UI-09, UI-10
US12Como vecina en el banco, quiero targets ≥44pxChat 390

MoSCoW (v1 L1)

PrioridadÍtems
MustChat bandos, compositor, acuse, login, kanban, ficha, estados, empty/error/loading, mesa mobile
ShouldRail S-SIDE con contadores; chips de tipo; pie «no es 112»; logout en mesa
CouldGET /api/me; foto en POST; filtro por tipo en mesa; ?view=flat
Won’tCuenta vecina, GIS, 112, cita, expediente SIA, foro vecinal, brigada JWT

6. Journey (VECINA · farola de la plaza + OPERADOR)

FaseActorAcciónTouchpointEmoción [HIPÓTESIS]
1 Leer el pregónRosaAbre /; lee 3 bandosChat H-CHATOficio, «esto no es la sede»
2 EscribirRosaCalle, farola, texto, su nombreComposerConcentración (banco, sol, letra grande)
3 Enviado ≠ resueltoRosaVe el código; lee el disclaimer/ok/:codeControl (H2 + microcopy)
4 No es 112RosaNo llama al chat por un infartoCopy + bnd_3Límite claro
5 Entrar a mesaNuriaLogin JWT; aterriza en kanban/login/mesaOficio, «tres columnas»
6 TriajeNuriaAbre PG-2401; pasa a En curso/mesa/PG-2401Control (H3)
7 Cerrar (más tarde)NuriaLa brigada avisa fuera; Nuria cierraMesaCierre de mesa, no milagro de farola
8 No citarNuriaNo hay slot; Rosa no espera llamada a las 10Ausencia de UIIntegridad

Momentos de verdad

  1. 3 bandos + compositor en el primer viewport — si hay que «descubrir la marca», se rompe H-CHAT / AP-21. [HIPÓTESIS]
  2. Sin cuenta — si aparece «regístrate», S2 / H3 fallan.
  3. Success dice que enviado no es resuelto — evita la mentira de la farola apagada.
  4. 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.)
  5. Kanban de 3 columnas, rail a la izquierda — S-SIDE / F-KAN. [HIPÓTESIS]

7. Service blueprint (resumen)

CapaElementos
Frontstage VECINAChat, bandos, composer, acuse. Sin mesa
Frontstage OPERADORLogin, kanban, ficha, cambio de columna
BackstageBrigada de alumbrado / limpieza (fuera), 112 (fuera), 010 (fuera), WhatsApp de barrio
SistemasNest API :3020, Neon empty-surf-15969880, JWT, Angular
SoportesSeed 1 OPERADOR + 3 bandos + 7 partes + fotos, Paper, docs
Fallos0 bandos; 401 en mesa; 404 código; red caída → UI error; validación de parte

Fallos de servicio y respuesta de diseño

FalloEvidencia de UIRecuperación
0 bandosUI-08 Empty chat«Aún no hay bandos del distrito.» + compositor igual visible
0 partes en una columnaUI-08 Empty columna«No hay partes en Recibido.» (sin CTA de cita)
API caídaUI-09 ErrorReintentar
Latencia chatLoading (doc 16)Skeleton hilo (3 bloques bando + barra composer)
Form inválidoInline fieldNo navegar a /ok
Código inexistente en acuseAcuse genéricoMuestra el código de la URL + copy; no inventa el parte
Código inexistente en mesa404«Ese parte no está.» + volver a Mesa
Sin token en /mesaRedirect/login
POST spam429 ShouldRate limit L+1

8. Matriz hallazgo → requisito → feature

HallazgoTipoRequisitoFeature v1
Escribe desde el banco, poco digital[SUPUESTO] S2Chat 390, targets ≥44, sin cuentaH-CHAT · UI-02
No sabe qué formulario es[HIPÓTESIS] H1Un compositor, no 12 camposPOST reports
Enviado se lee como resuelto[HIPÓTESIS] H2Copy irrenunciableUI-04 Success
El canal se lee como 112[DECISIÓN]Copy + bandoUI-01 + bnd_3
El parte se lee como expediente[DECISIÓN]Copy irrenunciableChat + mesa
Cita el día 1[DECISIÓN]Cero slot«PREGÓN no cita»
Operadora no quiere GIS[SUPUESTO] S4Calle en textoComposer + ficha
Vecino no necesita login[HIPÓTESIS] H3Público POST + JWT solo mesaGuards
L1 compacto, no SIA[DECISIÓN]3 columnas, 7 partesF-KAN

9. Preguntas abiertas (no bloquean v1)

IDPreguntaCó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étodoMuestra orientativaPregunta
Test de usabilidad chat 3905–6 vecinas 55–75¿Envían un parte en < 60 s sin cuenta?
Shadowing de mesa1 operadora de distrito¿Tres columnas sustituyen la bandeja?
Revisión de expectativa 112Clics vs «¿es una emergencia?»Validar H2
Recorrido con VoiceOver + letra grande5 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

PrincipioAplicación
Chat firstLa home es el hilo. No hay landing de marca por delante
Público / mesaEl vecino escribe sin cuenta; Nuria entra con JWT
Hilo → acuseProfundidad 1 desde el compositor hasta el código
Mesa → ficha → columnaProfundidad 1 desde el kanban hasta el PATCH
Lenguaje de dominioParte, bando, código, Recibido / En curso / Cerrado — no «ticket», «lead», «expediente», «silla»
Mensaje no es expedienteEl parte no tiene número SIA ni plazos
Enviado se miraSuccess no tiene «Resuelto» ni «Citar»
Rail de estados en mesaS-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

AudienciaNodos relevantes
VECINA / VECINOChat → Ok. No ven mesa
OPERADORLogin → Mesa → Ficha · (puede leer el chat)
Junta / 112Ninguno 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)

ElementoDestinoNotas
Wordmark PREGÓN/No es hero; es sello del hilo
Pie / link Mesa/login o /mesa si hay tokenSecundario, grafito, no CTA de marca
ComposerPOST → /ok/:codeAnclado 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)

SlotDestinoVisibilidadNotas
Recibido/mesa foco columna 1AuthContador seed 3
En curso/mesa foco columna 2AuthContador seed 2
Cerrado/mesa foco columna 3AuthContador seed 2
Salirlimpia token → /loginAuthAl 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)

SuperficieIzquierdaCentroDerecha
Chat /WordmarkMesa (ghost)
OkWordmarkEscribir otro
LoginWordmark
MesaWordmark en railNuria · Salir
Ficha← MesaCó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)

ElementoDestinoNotas
Email / password / EntrarPOST login → /mesaJWT
Errorno navegaInline
Pieno «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

PantallaContenidos
Chat /Wordmark, eslogan, 3 bandos, hilo, compositor (nombre, calle, tipo, texto), disclaimers, empty, loading, error
ComposerChips farola/cubo/banco/acera/otro + campos + Enviar
Success /ok/:codeCódigo, «enviado ≠ resuelto», «no es 112», CTA escribir otro
Login /loginWordmark, eslogan, email, password, submit, error, volver al chat
Mesa /mesaRail S-SIDE, 3 columnas, cards de parte (código, tipo, calle, autora), empty, loading, error
Ficha /mesa/:codeCódigo, foto, calle, tipo, cuerpo, autora, fecha, chips de columna, disclaimers
EmptyChat sin bandos · columna sin partes
ErrorRed
LoadingSkeletons de hilo / columnas
Mesa mobileSegmentos + lista

5. Taxonomía

Role

Status APILabel UISemántica
OPERADOROperadoraÚnico rol autenticado v1

[DECISIÓN] No hay VECINA / JUNTA / BRIGADA / COLORISTA.

ReportStatus

Status APILabel UISemántica
RECIBIDORecibidoEntró; nadie lo ha tomado
CURSOEn cursoNuria lo atiende; no es resuelto
CERRADOCerradoLa 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

APILabel UI
farolaFarola
cuboCubo
bancoBanco
aceraAcera
otroOtro

Bandos (líneas seed)

idTítuloSemántica
bnd_1Corte de aguaAviso de servicio
bnd_2Recogida de enseresAviso de servicio
bnd_3Esto no es el 112Límite de producto

[COMPROBADO] Tres bandos del encargo. [DECISIÓN]

Partes (líneas seed)

codekindstatusAutora/autor
PG-2401farolaRECIBIDORosa Méndez
PG-2402cuboRECIBIDOAndrés Peña
PG-2403aceraRECIBIDORosa Méndez
PG-2404bancoCURSOAndrés Peña
PG-2405farolaCURSORosa Méndez
PG-2406cuboCERRADOAndrés Peña
PG-2407otroCERRADORosa Méndez

[COMPROBADO] Siete partes, 3/2/2, del encargo.

6. Modelo mental vs UI

Modelo mentalRepresentació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

RutaQueryUso
/login?next=/mesa o /mesa/PG-2401Redirect post-login sanitizado
/mesa?col=recibido|curso|cerrado (Should)Foco de columna; Must en 390 (UI-11)
RestoSin ?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

UIAPI
Chat / bandosGET /api/bandos público
Enviar partePOST /api/reports público
LoginPOST /api/auth/login
MesaGET /api/reports JWT
FichaGET /api/reports/:code JWT
Mover columnaPATCH /api/reports/:code JWT
SaludGET /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

IdeaPor qué no en L1 v1
/explorar mapa de farolasRompe S4 / H-CHAT; eso es GIS
Área «mis partes» con cuentaCuenta de facto; contradice H3
Feed de partes ajenos en el chatPrivacidad de calle; foro vecinal
Search como homeH-SEARCH de otros días
Hoy bento de sillasH-BENTO de MATIZ
Wizard de 3 pasos (zona / tope)ZAGUÁN
Lookbook editorialH-GALLERY de MARGEN
/cita / /112Rompe microcopy
Bottom tabs Chat · Mesa · CuentaMezclaría oficios; el vecino no tiene mesa
Top-nav Servicios / Sede / ActualidadMarketing 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
PasoAcciónSistema
1Aterriza en el chatRouter público
2Lee bandos; ve compositorUI-01 / UI-02
3No 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
PasoAcciónSistema
1Lee corte de agua, enseres, «no es 112»bandos
2Entiende que el hilo es del distritoUI

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

CasoComportamiento
0 bandosUI-08: «Aún no hay bandos del distrito.» + compositor
Red / 5xxUI-09 + Reintentar (repite GET)
Primera cargaSkeleton hilo (doc 16)

F2 — Enviar parte (Guest) · F-CHAT

/ composer
  → street, kind, body, authorName
  → POST /api/reports
  → 201 Report { code, status: RECIBIDO, ... }
  → /ok/:code
CampoValidación cliente (mín.)API
authorNamerequired, min 2, max 80@MinLength(2)
streetrequired, min 4, max 120@MinLength(4)
kindrequired, enumfarola | cubo | banco | acera | otro
bodyrequired, min 10, max 500@MinLength(10)
statusno en POST; siempre RECIBIDOservidor fuerza RECIBIDO
codeno en POSTservidor 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]

GestosResultado
Chip tipoRadio único; aria-pressed
EnviarPOST; pending en botón
InválidoInline; no POST
Doble submitBotón disabled mientras pending

Errores

CasoComportamiento
Validación DTO400 + mensaje de campo
Red caídaError de red en form; no navegar a /ok/…
429Should: «Espera un momento para escribir otro.»
Kind fuera de enum400

[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 → /
PasoAcciónSistema
1Lee código y copyUI-04
2No abre la mesaGuard
3Puede escribir otroRouter

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
CampoValidación clienteAPI
emailrequired, email@IsEmail()
passwordrequired, min 6@MinLength(6)

Éxito: token guardado; user role=OPERADOR, nombre Nuria Delgado.
Credencial demo: nuria@pregon.ayto / password123.

CasoComportamiento
Credenciales inválidas401 + mensaje
Red caídaError de red en UI
Token caducado en mesa401 en GET → re-login
next externo / //Ignorado; va a /mesa
Vecina en loginNo 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
PasoAcciónSistema
1Lee las 3 columnasreports agrupados
2Lee código, tipo, calle, autora en cada cardReportCard
3Abre fichaRouter

É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]

CasoComportamiento
Columna 0UI-08 de esa columna
Red / 5xxUI-09 + Reintentar
Primera cargaSkeleton 3 columnas (doc 16)
Sin tokenRedirect /login
401 mid-sessionRedirect /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
PasoAcciónSistema
1Lee el parteReport
2Mueve columnaPATCH
3Lee disclaimerUI

Éxito: ficha leíble; disclaimer visible; status actualizado.
Error: 404 si code inexistente; 401 sin token.

AC ficha

#Criterio
1Foto del cover si existe (assets/partes/:file); si no, placeholder de tipo
2Código en Ibarra Real Nova; calle en Jost
3Cuerpo completo visible
4Disclaimer «mensaje ≠ expediente» visible sin scroll en desktop
5Sin CTA de cita, 112-como-envío, expediente SIA

Transiciones permitidas

DesdeHacia
RECIBIDOCURSO, CERRADO
CURSORECIBIDO, CERRADO
CERRADOCURSO (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
CasoComportamiento
SalirBorra pregon_token + pregon_user
Tras salir, /mesaRedirect 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ódigoCuándoUI
400DTO inválidoMensaje campo / genérico
401Sin/mal token en mesaRe-login
404code no existe (JWT)Mensaje + volver a Mesa
429Rate limit POSTEspera
5xx / networkAPI caídaError + 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:

EntidadTabla SQLPropósito
UserusersIdentidad de oficio (rol OPERADOR)
SessionsessionsSesión JWT / token persistido (Should)
BandobandosMensaje anclado del distrito en el chat público
ReportreportsParte 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

ValorDescripción
OPERADOROperadora de la mesa; único rol autenticado v1

ReportStatus

ValorDescripción
RECIBIDOEntró; nadie lo ha tomado
CURSOEn curso; no implica resuelto en la calle
CERRADOLa 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.
reports no apunta a users: el parte es anónimo de cuenta.

4. Tablas

users

CampoTipoConstraints
idTEXTPK
emailTEXTUNIQUE NOT NULL
password_hashTEXTNOT NULL (bcrypt)
nameTEXTNOT NULL
roleTEXTNOT NULL DEFAULT 'OPERADOR'
district_nameTEXTNOT NULL DEFAULT 'Distrito Usera / Villaverde'
created_atTIMESTAMPTZDEFAULT now()
updated_atTIMESTAMPTZDEFAULT now()

sessions

CampoTipoConstraints
idTEXTPK
user_idTEXTNOT NULL FK → users(id) ON DELETE CASCADE
token_hashTEXTNOT NULL
expires_atTIMESTAMPTZNOT NULL
created_atTIMESTAMPTZDEFAULT 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

CampoTipoConstraints
idTEXTPK
titleTEXTNOT NULL
bodyTEXTNOT NULL
pinnedBOOLEANNOT NULL DEFAULT true
created_at / updated_atTIMESTAMPTZ

[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

CampoTipoConstraints
idTEXTPK
codeTEXTUNIQUE NOT NULL · ^PG-\d{4}$
streetTEXTNOT NULL
kindTEXTNOT NULL CHECK IN (farola,cubo,banco,acera,otro)
bodyTEXTNOT NULL
statusTEXTNOT NULL DEFAULT 'RECIBIDO'
author_nameTEXTNOT NULL
coverTEXTNULL · filename (farola.jpg)
created_at / updated_atTIMESTAMPTZ

[DECISIÓN] SQL snake_case (author_name); JSON camelCase (authorName).

5. Reglas de integridad y negocio

ReglaImplementación
Lectura de bandosPública
Alta de partePública · status forzado RECIBIDO · code asignado
Lectura de mesa / fichaJWT OPERADOR → 401 sin token
PATCH statusJWT OPERADOR → 401 sin token
Login / healthPúblicos
PasswordNunca en claro; solo password_hash
Orden bandoscreated_at asc, pinned true
Orden reports en columnacreated_at desc
Orden groupedrecibido / curso / cerrado
POST Reportno marca CERRADO; no cita; no llama al 112
Público nunca lista reportsno hay GET público de partes
Cover allowlistfilenames seed; POST v1 no sube foto
Códigosecuencial 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

CampoTipoReq
streetstring 4–120
kindfarola | cubo | banco | acera | otro
bodystring 10–500
authorNamestring 2–80

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:

QueryResultado
(ninguna)agrupado recibido / curso / cerrado
?view=flatReport[] newest first
?status=RECIBIDOReport[] 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

NombreEmailPasswordDistrito
Nuria Delgadonuria@pregon.aytopassword123Distrito Usera / Villaverde

[COMPROBADO] Credencial del encargo. Nombre de pila es constructo de diseño.

Bandos (3 · pinned)

idTítuloCuerpo
bnd_1Corte de aguaJueves 21, de 10 a 14 h, en Marcelo Usera, tramo plaza Elíptica.
bnd_2Recogida de enseresSábado 23, de 9 a 14 h. Dejadlos junto al cubo, no en el banco.
bnd_3Esto no es el 112PREGÓ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]

codekindstatusAutora/autorCalleCoverCuerpo (demo)
PG-2401farolaRECIBIDORosa MéndezPlaza Elíptica, banco de la plazafarola.jpgLa farola del banco no enciende desde anoche. Estoy sentada debajo y no hay luz.
PG-2402cuboRECIBIDOAndrés PeñaCalle de Marcelo Usera 42cubo.jpgCubo de orgánicos rebosando. Lleva dos días. Huele.
PG-2403aceraRECIBIDORosa MéndezAv. de Rafaela Ybarra, esquina Clementeacera.jpgBaldosa suelta. Casi me caigo al bajar del bordillo.
PG-2404bancoCURSOAndrés PeñaPlaza de Ágata, banco del olivobanco.jpgTabla del asiento rota. Hay clavos.
PG-2405farolaCURSORosa MéndezCalle de la Resina 18, paso de cebrafarola.jpgLa farola parpadea junto al paso. De noche da miedo cruzar.
PG-2406cuboCERRADOAndrés PeñaAv. de Andalucía, frente al mercadocubo.jpgCubo de envases tumbado. (Mesa: recolocaron.)
PG-2407otroCERRADORosa MéndezCalle de Amparo, muro del colegioacera.jpgValla 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):

codeHora demo
PG-240708:05 · ya cerrado
PG-240608:20 · ya cerrado
PG-240508:40 · en curso
PG-240409:00 · en curso
PG-240309:15 · recibido
PG-240209:30 · recibido
PG-240109: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)

CambioNivel
Acuse público mínimo { code, status }L+1
Foto en POST (allowlist / upload)L+1
Nota interna de NuriaL+1
Segundo OPERADORL+1 / L3
Soft delete + audit logL3
GIS / mapaFuera (S4)
Cuenta de vecinoFuera (H3)
Expediente SIA / 112Fuera (microcopy)

06-tech-stack.md

06 — Stack tecnológico — PREGÓN

1. Visión

CapaTecnologíaNotas
FrontendAngular standalone + signalsPuerto 4200 · web responsive
EstilosTailwind CSSTokens highlighter (fog, tinta, mark, card, grafito, lacre)
BackendNestJS TypeScriptPuerto 3020, prefijo /api
PersistenciaPostgreSQL (ensureSchema o Prisma)Tablas users, bandos, reports (+ sessions Should)
DBNeon PostgreSQLproject empty-surf-15969880
AuthJWTRol OPERADOR (mesa); chat y POST reports públicos
DiseñoPaper01M0EZQ31KF04PAQ5RMCV7DQ2F
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:

ScriptAcción
apiArranca Nest en :3020
start / webArranca Angular en :4200

3. API pública vs JWT

MétodoRutaAuth
GET/api/healthPúblico · { ok: true, service: "pregon-api" }
GET/api/bandosPúblico
POST/api/reportsPúblico
POST/api/auth/loginPúblico
GET/api/reportsJWT OPERADOR
GET/api/reports/:codeJWT OPERADOR
PATCH/api/reports/:codeJWT 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ónRazón
Angular + NestAlineación con la serie daily y el handoff del estudio
Neon serverlessVolumen bajo (S1: 8–20 partes/día; demo 7 + 3 bandos); no cola Redis
JWT OPERADORD-P1-05: mesa interna cerrada; el chat sí es público
Auth en servidor para mesaSource of truth = guard; el cliente no «esconde» partes
Puerto 3020Evitar 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-XXXXAcuse humano, no UUID en la plaza

5. Variables

.env local (nunca git):

VariableServicioDescripción
DATABASE_URLAPINeon empty-surf-15969880
JWT_SECRETAPIFirma tokens
PORTAPIopcional, 3020
JWT_EXPIRESAPIopcional (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 (jose o @nestjs/jwt)
  • bcrypt (o bcryptjs)
  • 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

NoPor qué
npm workspacesAP-06
Firebase AuthStack fijo JWT + Neon
Next.js / ReactD-P0-03
Cl@ve / certificadoPREGÓN no es sede
GIS / PostGIS MustS4: calle en texto
Elasticsearch7 partes no lo necesitan
SIA / tramitadorMensaje ≠ expediente
112 / CADPREGÓ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

EjeNo
SuperficieFog #F1F3F6, ficha blanca, tinta de selloYeso MATIZ, hormigón ZAGUÁN, papel MARGEN, musgo CESTA, cromo RONDA
TipoIbarra Real Nova display + Jost UILibre Baskerville, Nunito, Instrument, Sora, Cormorant, Public Sans, Spectral, Manrope
FotoFarola, cubo, banco, acera: calle real, luz planaSmart-city drone, «familia feliz en la plaza», iconos 3D de farola
GridHilo de chat + compositor; mesa 3 columnas + railHero 2-col + 3 cards de features (AP-12)
AcentoMark #C8F000 en enviar, columna activa, pin de bandoAmatista chapel, óxido inmobiliario, cadmio editorial
ChromeRail de estados en mesa; chat sin top-nav de marketingBottom tabs, masthead de periódico, wizard 1—2—3, sidebar CRM genérica

3. Paleta

TokenHexRol
Fog / G-FOG#F1F3F6Ground de página. Fotocopia fría, no beige.
Tinta#12141ATexto, wordmark, cuerpo. Casi negro de sello.
Mark#C8F000Rotulador: CTA Enviar, columna activa, pin, código marcado
Ficha#FFFFFFSolo cards (bando, parte, composer). Nunca el fondo de página.
Grafito#5C6370Meta, labels, rail idle, hora, calle secundaria
Lacre#C1121FAcento 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

UsoFamiliaTamaños de referencia
WordmarkIbarra Real Nova 70024–28 desktop · 22 mobile
Titular de chat / mesaIbarra Real Nova 70032–40
Código PG-XXXXIbarra Real Nova 70028–36
Título de bandoIbarra Real Nova 70020–24
Cuerpo de bando / parteJost 40016–17 / 26–28 lh
UI, chips, form, listaJost 400/500/60014 / 16
Meta (hora, calle, tipo)Jost 50012–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:

AssetLectura visualUso
hero.jpgFotocopia / bando / plaza, no smart-cityCover UX · atmósfera, no hero de marketing en /
partes/farola.jpgFarola de barrio, noche o penumbraPG-2401 · PG-2405
partes/cubo.jpgCubo de callePG-2402 · PG-2406
partes/banco.jpgBanco de plazaPG-2404
partes/acera.jpgBaldosa / aceraPG-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 ]                     │
└─────────────────────────────────┘
  1. Wordmark + eslogan (una línea, no portada).
  2. Lead en el fold: 3 bandos (título Ibarra, cuerpo Jost).
  3. 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

SuperficieSpec
Hover cardRule tinta; no elevación 8px ni scale 1.04
EnvíoEl compositor se vacía; fade 160ms al acuse; off si reduced-motion
Cambio de columnaBadge mark, no confetti ni «sirena»
SkeletonPulso 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 / tizaPrimer 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» stockMentira social del «ya está arreglado»
Azul institucional 010Sede; contradice R-IN highlighter
Iconos outline de farola 2014Chrome 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

  1. Ground de página = #F1F3F6. Cards = #FFFFFF. Nunca al revés.
  2. Mark en el CTA Enviar y en la columna activa, no un header lima lleno.
  3. 3 bandos visibles en el primer viewport mobile (tras wordmark + eslogan).
  4. Ninguna fuente de la lista vetada. Ibarra Real Nova + Jost.
  5. Home reconocible como hilo, no como hero 2-col de smart city.
  6. Success no celebra un «ya está resuelto» ni un 112 atendido.
  7. 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):

KeyValor
colors.fog#F1F3F6
colors.ink#12141A
colors.mark#C8F000
colors.card#FFFFFF
colors.graphite#5C6370
colors.seal#C1121F
fontFamily.displayIbarra Real Nova
fontFamily.sansJost

[COMPROBADO] Hex y tipo = brief.
[COMPROBADO] Leftover Tailwind = chapel MATIZ. Contrato: tokens de esta tabla.

2. Escala tipográfica

TokenFamiliaSize / lhUso
display-lIbarra 70040 / 44Titular mesa desktop · código acuse
display-mIbarra 70028 / 32Código card · wordmark
display-sIbarra 70022 / 28Título bando 390
codeIbarra 70024 / 28PG-XXXX en card
bodyJost 40016 / 26Bando, parte, disclaimers
uiJost 40016 / 24Form, lista, composer
metaJost 50013 / 18Hora, calle, tipo
railJost 60012 / 16 · tracking 0.04emRecibido · En curso · Cerrado

3. Componentes

Composer (H-CHAT)

SpecValor
Posición 390Anclado inferior, sobre fog, card blanca
Posición desktopColumna derecha o bloque bajo el hilo, max 720
CamposauthorName, street, body · labels visibles
Tipos5 chips radio · target ≥44
CTAEnviar · 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)

ColumnaContenido
CódigoIbarra 24 · PG-2401
Tipochip meta
CalleJost 16
Autorameta grafito

Toda la card es un <a> a /mesa/:code. Hover: rule 1px ink. Sin lift.

Rail (S-SIDE)

SpecValor
Ancho desktop220–260px fijo izquierdo
FondoFog con rule 1px grafito 30%
Ítem activoJost 12 · ink + filete 2px mark izquierda
Ítem idleGrafito
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

BloqueSpec
Columnamax-width 720px
Cover4:3, max 320px
Códigodisplay-l / display-s
Calle + tipometa
Cuerpobody
Disclaimer«El mensaje no es un expediente. PREGÓN no cita.»
Status3 chips · aria-pressed · PATCH

Botones

VarianteUsoSpec
PrimaryEnviar, EntrarFondo mark, texto ink, h-44, radius 2
SecondaryEscribir otro, volverRule ink, texto ink, fondo fog
GhostMesa (en chat), Salir, ReintentarTexto grafito
SealAviso 112 (no es CTA de envío)Texto seal, no botón de acción primaria
DangerNo 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

EstadoReceta
Empty chatTitular Ibarra 28 + 1 línea Jost + compositor visible
Empty columnaTitular + 1 línea · «No hay partes en Recibido.»
ErrorBanner filete lacre + texto + Reintentar
Loading chat3 bloques bando + barra composer
Loading mesa3 columnas × 2 cards
404 parte«Ese parte no está.» + link a Mesa

4. Grid y breakpoints

ViewportComportamiento
≥1200Chat: hilo 7 / composer 5 o hilo max 720 centrado; mesa rail + 3 col
960–1199Mesa rail + 3 col compactas
600–959Chat 1 col; mesa segmentos + lista
~390Chat 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.

SitioRecurso
TiposTexto del chip. Icono trazo 1.5 de 20px opcional
Pin bandoFilete mark, no chincheta clip-art
EmptyNada o una rule mark de 32px
StatusTexto 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

SuperficieTratamiento
PáginaFog plano
Card / bando / parteBlanco plano + rule 1px grafito 30%
ListaFilas separadas por rule
ModalNo hay modal Must

Cero sombras shadow-lg. La fotocopia no flota.

7. Mapa componente → ruta

ComponenteRutas
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

  1. Tokens Tailwind = hex del brief, sin yeso MATIZ #E6E4ED como ground.
  2. Cards en blanco; página en fog.
  3. Primary CTA mark + texto ink (nunca grafito sobre fog como único CTA).
  4. Ibarra Real Nova no aparece en labels de form ni en el rail.
  5. Recorte de parte aspect-[4/3], no 1:1 de retrato ni 16:10 editorial.
  6. Targets de chips, rail, segmentos y CTA ≥44px.
  7. Composer anclado no tapa el último bando (padding-bottom).
  8. 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

UsarNo usar
Parte / mensaje / códigoExpediente, procedimiento, SIA, «ticket IT»
Enviado / recibidoResuelto (al enviar), «ya está arreglado», «cerrado» en acuse
En curso / cerrado (mesa)«Solucionado», «completado al 100%»
Bando / pregón / distritoNewsletter, feed, muro, «novedades del ayto»
Farola / cubo / banco / aceraIncidencia GIS, «activo patrimonial», SKU
Mesa / operadoraInbox, CRM, backoffice, kanban (en UI; sí en docs)
PREGÓN no cita / no es 112Pedir 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ónDóndeCopy
Mensaje ≠ expedienteComposer sobre Enviar · ficha de parte · pie de mesa«Esto es un mensaje, no un expediente. PREGÓN no abre un procedimiento.»
Enviado ≠ resueltoAcuse · composer pie«Enviado no es resuelto. El acuse no apaga la farola.»
PREGÓN no citaFicha · login pie · empty mesa«PREGÓN no cita. No hay hora ni “te llamamos a las 10”.»
PREGÓN no es 112Chat 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

SuperficieCopy
EsloganEscríbele al distrito.
Línea de mesaDistrito Usera / Villaverde
Chat titularPREGÓN
Bando metaDistrito
Composer nombreTu nombre
Composer calleCalle o plaza
Composer textoQué pasa
Composer tiposFarola · Cubo · Banco · Acera · Otro
Composer submitEnviar
Composer disclaimer 1Esto es un mensaje, no un expediente.
Composer disclaimer 2PREGÓN no es 112.
Login titularEntrar a la mesa
Login ayudaSolo operadora. El vecino escribe en el chat, sin cuenta.
Login submitEntrar
Login errorEmail o contraseña no valen.
Login volverVolver al chat
Empty chatAún no hay bandos del distrito. Puedes escribir un parte igual.
Empty RecibidoNo hay partes en Recibido.
Empty En cursoNo hay partes en curso.
Empty CerradoNo hay partes cerrados.
Success titularEnviado.
Success códigoPG-2408
Success cuerpoEnviado no es resuelto. El acuse no apaga la farola.
Success 112PREGÓN no es 112.
Success CTAEscribir otro
ErrorNo hemos podido cargar el pregón.
Error retryReintentar
Loading chatAbriendo el hilo…
Loading mesaAbriendo la mesa…
404 parteEse parte no está.
Mesa titularMesa
RailRecibido · En curso · Cerrado
Ficha volver← Mesa
Ficha disclaimerEl mensaje no es un expediente. PREGÓN no cita.
Ficha statusRecibido · En curso · Cerrado
SalirSalir
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.

PiezaTono
bnd_1 aguaHora y tramo. Sin «lamentamos las molestias».
bnd_2 enseresDónde dejarlos (cubo, no banco). Oficio.
bnd_3 112Límite. Sin dramatismo.
PG-2401 farolaRosa, desde el banco, concreto.
PG-2402 cuboAndrés, corto, dos días, huele.
PG-2403 aceraRosa, casi me caigo, baldosa.
PG-2404 bancoAndrés, clavos, olivo.
PG-2405 farolaRosa, paso de cebra, de noche.
PG-2406 cuboCerrado de mesa; el cuerpo sigue siendo lo que escribió Andrés.
PG-2407 otroRosa, 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

NombreUso
Rosa MéndezVECINA; sin cuenta; autora de 4 partes
Nuria DelgadoOPERADOR; nuria@pregon.ayto
Andrés PeñaVECINO digital; sin cuenta; autor de 3 partes
Distrito Usera / VillaverdeMesa 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

StatusFrase UIFrase que Nuria podría decir (fuera de app)
RECIBIDORecibido«Nos ha llegado. Aún no está en curso.»
CURSOEn curso«Lo estamos viendo. No está resuelto.»
CERRADOCerrado«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:45 en card; jueves 20 de agosto si hay cabecera de mesa.
  • No mezclar pg-2401 en 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

NoMotivo
«Tu expediente ha sido registrado»Mensaje ≠ expediente
«Ya está resuelto» como hecho de enviarEnviado ≠ 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 rotasLikes, otro producto
Lorem / «lorem ipsum farola»Prohibido en el case
Declaraciones inventadas de una junta realVerdad 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

TemaDecisión
Tipo UIJost ≥16px body
DisplayIbarra Real Nova no se usa por debajo de 20px
ContrasteTinta #12141A sobre #F1F3F6 / #FFFFFF
CTA markTexto ink sobre #C8F000
Lacre 112Texto seal sobre fog o seal + ink, nunca lacre sobre mark
FocoOutline 2px mark, offset 2px, visible (no outline-none global)
Rail<nav> + aria-current="page"
ComposerLabels visibles; chips aria-pressed; form en <form>
Imágenesalt = tipo + lugar
FormLabels visibles, no placeholder-only
ErroresTexto, no solo color
MobileTargets ≥44px (chips, Enviar, segmentos) · S2 banco de la plaza
MovimientoFade 160ms; prefers-reduced-motion: reduce → 0
Live regionsaria-live="polite" en empty/error de chat y mesa

2. Contraste (comprobación de diseño)

ParUsoNota
#12141A / #F1F3F6Body sobre fogPasa AA y AAA cuerpo
#12141A / #FFFFFFBody sobre fichaPasa AA y AAA
#12141A / #C8F000CTA EnviarPasa AA para texto ≥14px bold; no usar blanco sobre mark
#5C6370 / #F1F3F6Meta grafitoVerificar ≥13px; no usar en 11px
#5C6370 como texto 12px sobre blancoProhibido para body
Rail idle grafito 50%Solo si no es el único indicador+ label textual Recibido / En curso / Cerrado
#C1121F / #F1F3F6Aviso 112Verificar ≥16px; si falla, ink + rule lacre
Blanco / mark como textoProhibido (lima + blanco falla)

3. Teclado

FlujoOrden
ChatWordmark → bandos (lectura) → nombre → calle → tipos → texto → Enviar → link Mesa
OkCódigo (lectura) → Escribir otro
LoginEmail → password → submit → volver al chat
MesaRail Recibido / En curso / Cerrado / Salir → cards en orden de columna
FichaAtrá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

PiezaMarkup
Chat<main> · hilo <section aria-label="Bandos del distrito">
Composer<form aria-label="Escribir un parte">
Tiposrole="group" + aria-label="Tipo"
Bandos<article> por bando; pin decorativo aria-hidden
Rail<nav aria-label="Estados de la mesa"> · activo aria-current="page"
Kanban3 <section aria-label="Recibido"> etc.
Card parteun <a> por parte; incluye código, tipo, calle en texto
Status ficharole="group" + aria-label="Columna"
Aviso 112texto normal + borde lacre; no alert de envío
Skeletonsaria-busy="true" en el contenedor; aria-hidden en placeholders

5. Lector de pantalla — copy

SituaciónAnuncio
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.»
ErrorAnuncio 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 hacerLo que no afirma
Targets 44px en el banco de la plazaQue el banco de plaza Elíptica sea accesible de hecho
Chat usable con teclado, zoom 200% y letra 16pxQue 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 libreQue 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

RiesgoMitigación
Composer tapa el hilo en 390padding-bottom del main
Ibarra Real Nova en meta 12pxProhibido; meta = Jost 13
Mark como único estado de columna+ aria-current + label
Foto sin altChecklist seed: 4 alts + hero
112 leído solo por color lacreLabel siempre
Rosa poco digital (S2)Targets ≥44; no gestos swipe; no hover-only; sin cuenta
Chip lima + texto blancoTexto ink
Leftover chapel / Nunito 14pxTokens PREGÓN

8. QA a11y (mínimo)

#PruebaPasa si
1Teclado chat → composer → okSin trampa; foco visible
2Teclado login → mesa → ficha → chip statusAcciones alcanzables
3Zoom 200% móvil 390Chips usables; composer no solapa inputs
4Lighthouse a11y ≥ 90 en /Sin contrast fails de tokens
5VoiceOver/NVDA: bando y card anunciadosTítulo, código, calle comprensibles
6prefers-reduced-motionSin stagger ni slide
7Contraste CTA mark + aviso 112Texto ink sobre mark; 112 con texto

9. Criterios de aceptación

  1. Ningún control de chip, rail, segmento o CTA mide menos de 44×44 CSS px.
  2. El disclaimer no depende del color mark o lacre para ser entendido.
  3. Las 4 fotos de parte + hero tienen alt no vacío y no genérico.
  4. El foco no se pierde al reintentar un error de red.
  5. En 390 el composer queda por debajo del hilo (el último bando no queda tapado).
  6. Una mesa no se anuncia en / sin token (no hay HTML público de columnas).
  7. 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

DatoDóndeBase / nota
Email y hash de la operadorausersContrato interno de la mesa
Nombre de vecina en el partereports.author_nameTexto libre; no es una cuenta
Calle, tipo, cuerporeportsParte; catálogo seed · PII de atrezzo
Foto de incidentecover filenameSeed; no UGC v1
BandosbandosMensajes del distrito; no PII
Sesión JWTAuthorization + 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 v1Por qué
Email / teléfono / DNI de la vecinaH3 · no sede · no seguimiento
Geolocalización del dispositivoS4 · calle en texto
Coordenadas / GISS4
Foto subida por la vecinaNo hay UGC Must
Hora de citaPREGÓN no cita
Datos de 112 / salud / delitoPREGÓN no es 112
IP como identidadNo 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

ControlSpec
Auth mesaJWT Bearer · role === OPERADOR
Passwordbcrypt cost 12; nunca en response
Lectura mesaGET reports / :code → 401 sin token
Escritura statusPATCH :code → 401 sin token
Chat / POST parte / bandos / health / loginPúblicos
Rate limit/auth/login + POST /api/reports (anti stuffing / spam; Should si no llega al slice)
CORSOrigen http://localhost:4200 en local
SecretosSolo .env (no git)
Cover allowlistFilenames seed; no URL arbitraria
next post-loginPaths relativos allowlist (/mesa, /mesa/:code)
Guard webAngular 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

RiesgoMitigación v1
Enumeración de códigos PG-XXXXGET :code 401 sin token; acuse no devuelve calle
JWT en localStorageAceptable en demo; partes son atrezzo, no PII real de junta
Nombre de Rosa en seedEl 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 DBI01 de build: schema propio, no mezclar
Spam de POST reportsRate limit Should; body max 500
Nuria pide un GISS4: no hay fila geo; no se crea
Vecina pega un teléfono en bodyCopy: «qué pasa»; no se pide; no se scrape
Open redirect nextAllowlist /mesa

5. Roles y autorización (recordatorio)

RecursoAnónimoOPERADOR
GET bandos / health / POST reports / POST login
GET reports / :code401
PATCH status401
Cita / 112 / expedienteNo existeNo existe

Rosa, Andrés y la junta no tienen fila en users.

6. Criterios de aceptación

  1. Response de login no incluye password ni password_hash.
  2. GET /api/reports y GET /api/reports/PG-2401 sin Bearer son 401, no 200 [].
  3. POST /api/reports sin Bearer es 201 (público).
  4. GET /api/bandos sin Bearer es 200.
  5. No hay formulario público que pida DNI, email o teléfono.
  6. /mesa sin token redirige a /login (no renderiza columnas en HTML anónimo).
  7. /ok/:code no llama a GET JWT.
  8. .env no se commitea.
  9. 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

EventoCuándoProps mín.
chat_viewRender de /bandos (0–n)
report_createPOST 201code, kind
ok_viewRender de /ok/:codecode
operador_loginJWT ok
mesa_viewRender de /mesarecibido, curso, cerrado
parte_openApertura de fichacode, status
report_statusPATCH 200code, from, to
chat_empty0 bandos
error_bannerUI-09surface

2. Qué no se mide

No eventoMotivo
signup_click / lead_submitNo existe CTA de cuenta vecina
appointment_bookedPREGÓN no cita
emergency_callPREGÓN no es 112; no hay CTA de llamada como envío
expediente_openMensaje ≠ expediente
report_resolved_on_sendEnviado ≠ resuelto; el evento no existe
like / favoriteFuera
Identidad de Rosa como usuariaH3 · no es usuaria
body completo del partePII 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ñalCómo se observa
Mesa cerradaGET /reports 401 sin token (QA, no analytics)
Chat abiertoPOST reports 201 sin token
Expectativa de resueltoComentarios cualitativos; cero evento resolved-on-send
Expectativa de 112Cero evento de llamada como conversión
Salud4xx/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)

  1. Los eventos de §1 tienen nombre estable.
  2. No se dispara ningún evento de lead / cita / 112-como-conversión / expediente / resuelto-al-enviar.
  3. Un bloqueador de cookies no rompe el composer (analítica no es Must de runtime).
  4. kind se mide; body no.

13-qa-test-plan.md

13 — Plan de pruebas — PREGÓN

1. Smoke obligatorio (D-P1-06)

#AcciónEsperado
1GET /api/health200 · { ok: true, service: "pregon-api" }
2GET /api/bandos200 · 3 ids bnd_1, bnd_2, bnd_3
3POST /api/auth/login nuria@pregon.ayto / password123200 · accessToken · user.role=OPERADOR
4GET /api/reports con Bearer200 · agrupado 3/2/2 · códigos PG-2401…2407
5ng build o serve webWeb arranca o build OK

[COMPROBADO] Smoke = health (pregon-api) + bandos públicos + login JWT + reports JWT.

2. Casos funcionales

IDCasoEsperado
S1Códigos seedGET reports incluye PG-2401…PG-2407
S2Reports sin JWT401 (no 200 [])
S3Reports/:code sin JWT401
S4Bandos sin JWT200
S5POST reports sin JWT201 · status=RECIBIDO · code PG-XXXX
S6Code inexistente (con JWT)404
S7Chat UI/ muestra 3 bandos + compositor; cero cuenta / cita
S8Home es chat/ es H-CHAT; no hero 2-col; no Hoy bento MATIZ
S9Rutas mesa/mesa sin token → /login
S10PATCH status200 · RECIBIDO → CURSO
S11PATCH no edita cuerpoBody { street: "x" } ignorado o 400
S12Login malo401 · mensaje en form
S13Empty chat0 bandos → UI-08 + compositor
S14API caídaUI-09 + Reintentar conserva la vista
S15LoadingSkeleton hilo / kanban antes del primer paint
S16Mobile 390Composer + chips ≥44px; Enviar no tapado; mesa = lista por columna
S17Fotos seedcovers farola.jpg cubo.jpg banco.jpg acera.jpg resuelven
S18Password no viaja de vueltalogin response sin hash
S19Body POSTacepta street, kind enum, body, authorName
S20Kind inválidoPOST kind farola_led → 400
S21Success/ok/:code copy «Enviado no es resuelto» · «no es 112»
S22Health no es matizservice matiz-api
S23Conteos seedrecibido 3 · curso 2 · cerrado 2
S24/ok/:code sin JWT funciona200 de página; no pide login
S25Microcopy ×4mensaje≠expediente · enviado≠resuelto · no cita · no es 112
S26Puerto APIproceso en 3020, no 3019
S27Leftoverno se sirven /fichas ni /hoy como producto

3. Datos de prueba

UsoValor
OPERADORnuria@pregon.ayto / password123
Parte felizPG-2401 · Rosa · farola · RECIBIDO
Parte en cursoPG-2404 · banco
Parte cerradoPG-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 chatborrar bandos (solo entorno de test)
Empty columnaPATCH todos RECIBIDO a CURSO (test)

4. Regresión de diversidad / craft

CheckFalla si
Home es chatHay hero 2-col + 3 cards de marca (AP-12) o bento MATIZ o wizard ZAGUÁN
Shell mesa es S-SIDEHay bottom tabs como nav primaria de mesa, masthead o chrome 1—2—3
Copy honestoAcuse dice «resuelto» o ficha dice «expediente» o hay «pedir cita» o CTA 112 de envío
PaletaSe cuelan amatista MATIZ / óxido ZAGUÁN / cadmio MARGEN / tiza PIZARRA
TipoLibre Baskerville / Nunito / Instrument / Sora / Cormorant / Public Sans / Spectral / Manrope
RutasAparecen /fichas, /hoy, /cita, /112, /sede, /expediente, /mapa
GroundPágina en yeso #E6E4ED o blanco total en vez de fog #F1F3F6
Healthservice: "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 .env en 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 AngularArtboard
Tokens highlighter + Ibarra Real Nova / JostUI-00
Chat home 1440 bandos + hilo + composerUI-01
Chat mobile 390UI-02
Composer tipos farola/cubo/banco/aceraUI-03
Success código «enviado ≠ resuelto»UI-04
Login OPERADORUI-05
Kanban Recibido / En curso / CerradoUI-06
Ficha parte + foto + mover columnaUI-07
EmptyUI-08
ErrorUI-09
Loading skeletonsUI-10
Mesa mobile lista por columnaUI-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

TemaContrato
Health{ ok: true, service: "pregon-api" }
BandosGET /api/bandos público
Alta partePOST /api/reports público · body street, kind, body, authorName
Login{ accessToken, user } camelCase (no access_token)
MesaGET /api/reports JWT → { recibido, curso, cerrado }
FichaGET /api/reports/:code JWT
StatusPATCH /api/reports/:code JWT { status }
Status enumRECIBIDO | CURSO | CERRADO
Kind enumfarola | cubo | banco | acera | otro
RoleOPERADOR (no COLORISTA, no AGENT)
CodePG-XXXX

Campos JSON en camelCase hacia el web aunque SQL sea snake_case. [DECISIÓN]

4. Mapa de rutas web

PathGuard clientePágina
/noChatPage
/ok/:codenoOkPage
/loginno (si hay token → /mesa)LoginPage
/mesaauthMesaPage
/mesa/:codeauthPartePage

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

CheckOK
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

ProhibidoMotivo
npm workspacesAP-06
Commit de .envSecreto
Prometer expediente, resuelto-al-enviar, cita o 112Microcopy duro / AP-09
Estado SAVED / APPLIED / PUBLISHED / DRAFTVocabulario de otros días
Role COLORISTA / AGENT / EDITOREso es MATIZ / ZAGUÁN / MARGEN
Search bar o wizard como homeRompe H-CHAT
Hero 2-col + 3 cardsAP-12
Bottom tabs Hoy · Fichas · CuentaMATIZ leftover
image_gen como captura de productoD-P0-09
Body POST con oxidant / lines / zona / maxRentLeftover
Health matiz-apiEncargo pregon-api
Puerto 3019Encargo 3020

8. Contactos de diseño (artefactos)

ArtefactoPath
Briefux-projects/2026-08-20-pregon/docs/00-day-brief.md
Paperhttps://app.paper.design/file/01M0EZQ31KF04PAQ5RMCV7DQ2F
Datosdocs/05-data-model.md
Flujosdocs/04-user-flows.md
IXdocs/16-interaction-specs.md
Builddocs/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)

EntregaEvidencia
Definición producto + ternadocs 00-brief, 01
Personas Rosa / Nuria / Andrés, JTBD, journeydocs 02
IA chat-first + chrome S-SIDEdocs 03
Flujos F-CHAT + F-KANdocs 04
Modelo users / bandos / reports + seeddocs 05
Stack Angular/Nest/Neon/JWT :3020docs 06, 20
Dirección highlighter + DSdocs 07, 08
Copy Mensaje ≠ expediente · Enviado ≠ resuelto · no cita · no es 112docs 09
Paper ref 12 UX + 12 UIdocs 00-paper-reference
Suite 00–20 + README + executivecase 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)

ÍtemNotas
Acuse público mínimo { code, status }Sigue sin listar el parte entero
Foto en POST (allowlist)Sigue sin GIS
GET /api/meMesa pinta Nuria desde API
Nota interna de operadoraNo es chat al vecino
Rate limit login + POST reportsAnti stuffing / spam
Playwright S1–S27CI
API URL por environmentDeploy
prefers-reduced-motion verificadoa11y
Drag-and-drop columnas desktopMust sigue siendo chips en ficha
Borrador local del composersessionStorage; 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)

ÍtemPor qué
112 como canalPREGÓN no es 112
Cita / slot / «te llamamos»PREGÓN no cita
Expediente SIAMensaje ≠ expediente
Cuaderno de fórmulas / Hoy bento≠ MATIZ
Wizard + comparador de fincas≠ ZAGUÁN
Lookbook editorial≠ MARGEN
App nativaFuera de stack daily
Hero 2-col + 3 cardsAP-12

6. Orden de ataque si hay continuidad de código

  1. Contratos 05–06 + seed 05 + smoke 13 (alinear leftover MATIZ)
  2. Web paridad UI-00…11
  3. Acuse público mínimo + rate limit (L+1)
  4. Foto POST si hay campo
  5. Evaluar brief L3 (no parche silencioso)

16-interaction-specs.md

16 — Especificación de interacción — PREGÓN

1. Convenciones

TokenValor
Duración corta120–160ms
Easingease-out
Reduced motion0ms
Target≥44×44
Pendingcontrol 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

GestoResultado
Primera cargaLoading: 3 bloques bando + barra composer; aria-busy
200 con 3hilo + compositor. Lead: eslogan. 3 datos: los 3 bandos
200 con 0UI-08 empty · compositor sigue
Error 5xx / redUI-09 + Reintentar (repite GET bandos)
Scroll hiloComposer fijo en 390; no hide-on-scroll v1
Tap Mesa (ghost)/login o /mesa si hay token
Hover bando desktopNada (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)

CampoInteracción
authorNametext · autocomplete name
streettext
kind5 chips radio
bodytextarea 3+ filas
GestoResultado
Chip tipoaria-pressed único
EnviarPOST; pending; no navegar si inválido
Submit inválidoFoco al primer campo error; no POST
Error redBotón Reintentar; no limpia campos
Doble tap EnviarIgnorado mientras pending
201Navega /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)

GestoResultado
CTA primarioEscribir otro /
CopyVisible «Enviado no es resuelto» + código + «no es 112»
RecargaCódigo de URL + copy; sin hidratar GET JWT

Sin confetti. Sin farola que se enciende. Sin «cobrar». Sin «te llamamos».


5. Login (UI-05)

GestoResultado
SubmitPOST login; pending en botón
200Guarda token + user; navega next sanitizado o /mesa
401Texto bajo el form; password no se limpia
EnterSubmit
Volver al chat/
Primera cargaForm 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

GestoResultado
Primera cargaLoading: 3 columnas × 2 cards; aria-busy
200cards en Recibido / En curso / Cerrado
Columna 0UI-08 de esa columna
ErrorUI-09 + Reintentar
Tap card/mesa/:code (card completa clicable)
Hover desktopRule 1px ink; no elevación
Drag-and-dropNo Must v1
Tap rail RecibidoScroll/foco a columna 1 (desktop); ?col=recibido Should
Pull-to-refreshNo en v1

AC: código, tipo y calle visibles en cada card sin hover. Cero botones Citar / 112 / Expediente.


7. Ficha (UI-07)

GestoResultado
Tap ← Mesa/mesa
Chip statusPATCH { status }; pending en el grupo; aria-pressed único
200 PATCHChip actualiza; no navega fuera
Error PATCHBanner; status visual no miente (revierte chip)
FotoNo lightbox v1
Scroll cuerpoLectura normal; sin scroll-jacking

Sin «llamar ahora». Sin WhatsApp. Sin «citar». Sin «abrir expediente».


8. Empty (UI-08) · Error (UI-09) · Loading (UI-10)

EstadoInteracción
Empty chatTitular + 1 línea. Composer visible. Sin CTA de sede
Empty columnaTitular + 1 línea
ErrorBanner + Reintentar; foco al banner; no pierde form
Loading chat3 bloques + barra; no spinner centrado de marca
Loading mesa3 × 2 cards fog/card
Loading ficha1 foto 4:3 + 4 barras

9. Mesa mobile (UI-11)

GestoResultado
Tap segmentoCambia columna; lista de esa columna
Tap fila/mesa/:code
Swipe horizontal entre columnasNo Must (S2: Rosa no, y Nuria en 390 usa segmentos)
RailNo se pinta; segmentos ocupan su sitio

390: una columna a la vez. [DECISIÓN]


10. Rail (S-SIDE) desktop

GestoResultado
Tap Recibido / En curso / CerradoFoco de columna; aria-current
Tap SalirLimpia storage → /login
Tap wordmark (auth)/mesa
Scroll tableroRail fijo; no hide-on-scroll v1

11. Motion

SuperficieSpec
Cambio de rutaFade 160ms (off si reduced-motion)
BandosFade 160ms, stagger ≤30ms (off si reduced-motion)
Chip statusCrossfade 120ms
RailSin hide-on-scroll v1
SkeletonsEstá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

  1. Una vecina en 390px llega de abrir / a ver un código en ≤ 3 taps útiles (tipo → campos → Enviar).
  2. Ningún submit permite doble POST.
  3. El composer no cubre el H1 ni el último bando (spacer).
  4. Reduced-motion elimina stagger y slide.
  5. La home no es hero 2-col (AP-12).
  6. 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.
  7. / no presenta ningún control de cuenta, cita o expediente.
  8. El loading de chat es skeleton de hilo, no un logo municipal.
  9. El acuse muestra «enviado ≠ resuelto» y «no es 112» sin scroll en desktop.
  10. Nuria en 1440 mueve un parte a En curso en ≤ 3 taps (card → chip).
  11. Nuria en 390 usa lista por columna, no 3 columnas de 120px.
  12. 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.

RutaPaperRol
/UI-01, UI-02, UI-03, UI-08, UI-09, UI-10VECINA / Guest
/ok/:codeUI-04VECINA / Guest
/loginUI-05OPERADOR
/mesaUI-06, UI-08, UI-09, UI-10OPERADOR
/mesa/:codeUI-07OPERADOR
/mesa ~390UI-11OPERADOR

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)

  1. Abrir / sin token — chat público. Leer eslogan Escríbele al distrito. Leer 3 bandos (agua, enseres, no es 112).
  2. Composer: nombre Rosa Méndez, calle Plaza Elíptica, banco de la plaza, tipo Farola, texto La farola del banco no enciende desde anoche.
  3. Enviar/ok/PG-XXXX — «Enviado no es resuelto. El acuse no apaga la farola.» «PREGÓN no es 112.»
  4. Ghost Mesa/login. Entrar nuria@pregon.ayto / password123.
  5. /mesa — Recibido 3 (PG-2401 farola Rosa · PG-2402 cubo Andrés · PG-2403 acera Rosa) · En curso 2 · Cerrado 2.
  6. Tocar PG-2401 — ficha, foto farola, cuerpo, «El mensaje no es un expediente. PREGÓN no cita.»
  7. Chip En curso → PATCH. Volver a mesa: Recibido 2 / En curso 3.
  8. Comprobar que no hay botón Citar, 112-como-envío ni Expediente.
  9. Viewport 390: chat con composer anclado; mesa con segmentos + lista.
  10. Salir/login. / sigue siendo el chat.

Estados en el mismo prototipo

EstadoCómo provocarlo
Empty chat0 filas en bandos (entorno de test)
Empty columna0 reports de ese status
ErrorAPI caída / 5xx
Loadingthrottle de red en GET /bandos o /reports
404 parte/mesa/PG-9999 con token
401 mesa/mesa sin token → login
Chat 390390 × 844 en /
Chat 14401440 × 900 / (UI-01)
Mesa 390390 × 844 /mesa (UI-11)
Acuse recargadoF5 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

RequisitoEstadoEvidencia
Diversidad sector/tipo/nivelOKAdmin 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−2OKS-SIDE · H-CHAT · F-KAN vs MATIZ S-BOTTOM · H-BENTO · F-EDIT
Day brief + anti-patronesOKdocs/00-day-brief.md (no sobrescrito)
Paper ≥12 UX + ≥10 UIOKUX-count 12 · UI-count 12 · file 01M0EZQ31KF04PAQ5RMCV7DQ2F
Docs 00–20OKsuite en docs/ + executive + README
JWT OPERADOROKRole OPERADOR · D-P1-05 · mesa cerrada, chat público
API + seed + NeonOKcontratos empty-surf-15969880, port 3020, health pregon-api
Web tokensOKIbarra Real Nova + Jost · paleta highlighter
Chat-first (no AP-12 / AP-21)OKUI-01 / docs 03, 07, 08
Hipótesis no fake fieldOKetiquetas en doc 02
Copy ×4 irrenunciableOKdocs 01, 09, 16
Ground G-FOGOK#F1F3F6 · no G-CHAPEL / G-CONCRETE / G-GALLERY

3. Cobertura funcional

Feature briefSpecUI PaperAPIDocs
Chat bandos + compositorUI-01…03GET bandos · POST reports03, 04, 16
Acuse códigoUI-04POST 20104, 09
Login OPERADORUI-05POST login04, 06
Kanban 3 columnasUI-06GET reports03, 04, 16
Ficha + mover columnaUI-07GET/PATCH :code05, 16
EmptyUI-08200 []16
ErrorUI-095xx/red16
LoadingUI-10pending16, 17
Mesa mobileUI-11misma API04, 16
Design systemUI-0008
Seed 1 + 3 + 7 (3/2/2)seed05
Health pregon-apiGET health05, 06, 20

4. Cobertura Paper (literales)

IDNombreEn 00-paper-reference
UX-00Cover
UX-01Stakeholders
UX-02Personas
UX-03JTBD
UX-04Stories
UX-05Journey
UX-06Blueprint
UX-07Site map
UX-08Flujos
UX-09Datos+permisos
UX-10Métricas
UX-11Research
UI-00Tokens
UI-01Chat home
UI-02Chat mobile
UI-03Composer
UI-04Success
UI-05Login
UI-06Kanban
UI-07Ficha
UI-08Empty
UI-09Error
UI-10Loading
UI-11Mesa mobile

[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)

EjeScore 1–5Comentario
Diversidad5Chat+kanban admin vs PWA oficio MATIZ, wizard ZAGUÁN y galería MARGEN; L1 no L2
Craft visual (spec)4–5Highlighter + Ibarra/Jost + fotos de parte documentadas
Densidad UX docs5Suite L1: tablas, AC, etiquetas S/H/D
Completitud código L15Smoke :3020 health/bandos/login/GET+POST+PATCH reports + ng build; leftover MATIZ droppeado
Authz5Contrato: chat público; mesa 401; POST reports 201
Verdad investigación5Sin entrevistas ni stats de campo falsas

6. Huecos aceptados (no regresiones de cierre documental)

HuecoClasificación
GIS / 112 / cita / expediente / cuenta vecinaFuera L1
e2e automatizadoL+1
Foto en POSTL+1
Acuse público hidratadoL+1 / D9 v1
Leftover clients / formulas / hoy / matiz-api / :3019Build; 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ÍtemCapa
D01Definición producto PREGÓN + esloganDocs
D02Day brief diversidad admin pública L1Docs (preexistente, no pisado)
D03Terna R-IN · S-SIDE · H-CHAT · F-KANDocs
D04Personas Rosa Méndez / Nuria Delgado / Andrés PeñaDocs + Paper ref
D05JTBD + stories MustDocs
D06IA chat-first + chrome S-SIDEDocs
D07Flujos F0–F9 + ACDocs
D08Modelo users / bandos / reportsDocs
D09Contratos API :3020 · health pregon-apiDocs
D10Auth JWT OPERADOR · POST reports públicoDocs
D11Status RECIBIDO | CURSO | CERRADO · 401 mesaDocs
D12Composer calle + tipo + texto + nombreDocs
D13Microcopy ×4 (expediente / resuelto / cita / 112)Docs
D14North star partes a En curso en 24 hDocs
D15Tokens highlighter + Ibarra Real Nova / JostDocs
D16Paper UX-00…11 + UI-00…11Docs
D17Suite docs 00–20 + README + executiveDocs
D18Seed 1 OPERADOR + 3 bandos + 7 partes 3/2/2Docs
D19Assets listados (hero + 4 partes)README
D20QA smoke + S1–S27Docs
D21Neon empty-surf-15969880Docs
D22Chat 390/1440 + mesa rail/lista especificadosDocs
D23Ground G-FOG #F1F3F6Docs
D24Rutas reales solo las 5 del encargoDocs

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ÍtemPrioridadNotas
I01Alinear apps/api + apps/web al contrato PREGÓNP0quitar leftover MATIZ clients/formulas/hoy
I02Schema users / bandos / reportsP0
I03Seed Nuria + 3 bandos + 7 reports 3/2/2P0
I04GET bandos público · GET reports JWT agrupadoP0401 sin token en reports
I05POST reports público + PATCH :code JWTP0status RECIBIDO al crear
I06POST login JWTP0nuria@pregon.ayto
I07GET health service: "pregon-api"P0no matiz-api
I08Auth guard Angular solo /mesa y /mesa/:codeP0/ y /ok/:code públicos
I09Chat / ok / login / mesa / parteP0
I10Tokens Tailwind fog/ink/mark + fotos + railP0Ibarra + Jost
I11Smoke curl + buildP0D-P1-06
I12Puerto API 3020 · proxy y environmentP0no 3019

3. Backlog L+1 (polish)

IDÍtemPrioridadNotas
B01Acuse público mínimo { code, status }P1no listar body
B02Foto en POST allowlistP2
B03Rate limit login + POST reportsP1anti stuffing / spam
B04Playwright smokeP1CI
B05Skeleton UI-10 en códigoP2
B06GET /api/meP2
B07Borrador local del composerP3no es enviado
B08API URL por environmentP1deploy
B09Página 404 amigableP3
B10Sessions invalidablesP2
B11Drag-and-drop columnasP2chips siguen siendo Must
B12Nota interna OPERADORP3no chat al vecino

4. Backlog L3 (requiere brief nuevo)

IDÍtemDependencia
C01Multi-operador / turnosusers + membership
C02Cuenta VECINA + «mis partes»authz · rompe H3
C03GIS / mapaS4 se rompe · brief nuevo
C04Expediente SIA / sede / Cl@vemicrocopy se rompe
C05Integración 112 / 010PREGÓN no es 112 se rompe
C06Cita previaPREGÓN no cita se rompe
C07Foro vecinal / partes ajenosD14 se rompe
C08Dos mesas Usera ≠ VillaverdeS1 se relaja

5. Backlog explícitamente fuera

IDÍtem
E01112 como canal de producto
E02Cita / slot / «te llamamos a las 10»
E03Cuaderno de fórmulas / Hoy bento
E04Wizard + comparador de fincas
E05Lookbook editorial / agenda de crónicas
E06App nativa
E07Hero 2-col + 3 cards de marca

6. Explicitamente no-backlog

IdeaRazón
Hero 2-col + 3 cardsAP-12 / rompe H-CHAT
Bottom tabs Hoy · Fichas · CuentaS-BOTTOM de MATIZ
Chrome de pasos 1—2—3S-WIZ de ZAGUÁN
Masthead de periódicoS-TOP de MARGEN
Form «pide cita»PREGÓN no cita
«Ya está resuelto» al enviarEnviado ≠ resuelto
Health matiz-apiContrato pregon-api

7. Orden de ataque recomendado (continuidad de código)

  1. I01–I12 (vertical slice runnable, sin leftover MATIZ)
  2. B03 + B04 + B08
  3. B01 + B06
  4. B02 + B11 si hay campo
  5. Evaluar brief L3 — no parche silencioso

8. Trazabilidad

OrigenÍtems
Day brief must-haveD01–D24, I01–I12
Doc 15 L+1B01–B12
Doc 15 L3 / fueraC01–C08, E01–E07

20-implementation.md

20 — Implementación — PREGÓN

1. Resumen ejecutivo técnico

CampoValor
App path/Users/cristian/orca/pregon-app
APINestJS · puerto 3020 · prefijo /api
WebAngular standalone · puerto 4200
Packageapps independientes (npm --prefix o pnpm --filter)
DBNeon PostgreSQL · project empty-surf-15969880
Tablasusers, bandos, reports (+ sessions Should)
AuthJWT Bearer · role OPERADOR (mesa); chat y POST reports públicos
DominioUser, Bando, Report
Fecha2026-08-20
GitHubhttps://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

RolEmailPassword
OPERADORnuria@pregon.aytopassword123

Vecina / vecino: sin cuenta. Escriben en /.

3. Schema

Al boot o vía migrate:

  1. CREATE TABLE / migrate de users, bandos, reports (doc 05). Opcional sessions.
  2. Índices: reports(code), reports(status, created_at DESC), users(email), bandos(pinned, created_at).
  3. Seed idempotente:
    • upsert OPERADOR nuria@pregon.ayto (bcrypt de password123)
    • upsert bandos por id (bnd_1bnd_3)
    • upsert reports por code (PG-2401PG-2407)
  4. 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)

TablaContenido
usersOPERADOR Nuria
bandos3 anclados
reports7 partes 3/2/2
sessionsShould: 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 === OPERADOR protege GET/PATCH reports.
  • Públicos: health, bandos, POST reports, login.

Oficio

MétodoRutaAuthNotas
GET/api/healthNo{ ok: true, service: "pregon-api" }
GET/api/bandosNopinned true, created_at asc
POST/api/reportsNocrea RECIBIDO + code
GET/api/reportsJWTagrupado default; ?view=flat; ?status=
GET/api/reports/:codeJWT
PATCH/api/reports/:codeJWT{ 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áginaRutaResponsabilidad
ChatPage/GET bandos, composer, POST reports, empty/loading/error
OkPage/ok/:codecódigo + copy; state opcional; sin GET JWT
LoginPage/loginform → login → mesa
MesaPage/mesaGET reports, kanban / lista 390, empty/loading/error
PartePage/mesa/:codeGET :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ónRazón
Puerto API 3020Evitar colisión con MATIZ 3019
Tablas bandos / reportsDominio de pregón, no leftover clients
Role OPERADOREncargo; no COLORISTA
Health pregon-apiEncargo; no matiz-api
Chat público + mesa JWTS2 / H3 / D-P1-05
JSON camelCaseConvenio web serie daily
PATCH :code solo statusContrato del encargo
Success /ok/:code sin GETD9 enumeración
Cover allowlistEvitar URL arbitraria
Apps independientesAP-06
Home no usa hero 2-colAP-12 / H-CHAT
Kind enum cerradoS4
Composer sin foto MustEncargo POST 4 campos
Mesa 390 = listaUI-11

7. Variables de entorno

VariableServicioDescripción
DATABASE_URLAPINeon empty-surf-15969880
JWT_SECRETAPIFirma tokens
PORTAPIopcional, 3020
JWT_EXPIRESAPIopcional, default 12h

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

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

  1. Schema OK (tablas users, bandos, reports existen).
  2. GET /api/health 200 { ok: true, service: "pregon-api" }.
  3. GET /api/bandos 200 · 3 bandos.
  4. POST /api/auth/login 200 + accessToken + role=OPERADOR.
  5. GET /api/reports Bearer: agrupado 3/2/2, codes PG-2401…2407.
  6. GET /api/reports sin token 401.
  7. POST /api/reports sin token 201 status=RECIBIDO.
  8. PATCH /api/reports/PG-2401 { "status": "CURSO" } Bearer 200.
  9. 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
1User Nuria Delgado / nuria@pregon.ayto / password bcrypt de password123 / role OPERADOR
23 bandos bnd_1 bnd_2 bnd_3 pinned
37 reports PG-2401…2407 con kinds y covers de doc 05
4Conteos 3 RECIBIDO · 2 CURSO · 2 CERRADO
5Rosa autora de 2401, 2403, 2405, 2407
6Andrés autor de 2402, 2404, 2406
7Idempotencia: upsert por email / bando id / report code

11. Alineación case ↔ app

DocEvidencia esperada en código
05 data modelcolumnas y enums
03–04 IA/flowsroutes + controllers
08 DStailwind colors + fontFamily Ibarra/Jost
09 contentstrings en templates (microcopy ×4)
00 paperURLs en README case
11 securityGuard 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:

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