00-day-brief.md
00 — Day brief · 2026-08-18 · ZAGUÁN
Decisiones ALS-2 BRIEF
| Campo | Valor |
|---|---|
| Fecha | 2026-08-18 |
| Nombre | ZAGUÁN |
| Complejidad | Nivel 1 (no L2 consecutivo tras MARGEN) |
| Sector | Inmobiliaria de barrio (Usera / Vallecas / Entrevías / Orcasur) |
| Tipo | Wizard de criterios + comparador de 3 fincas + ficha + mesa AGENTE JWT |
| Plataforma | Web responsive (wizard/comparador desktop · pasos mobile · mesa agente) |
| Registro | R-IN — configurador usable, no web de agencia clásica |
| Shell | S-WIZ — chrome de pasos, sin top-nav de marketing |
| Home | H-WIZARD — la entrada es el paso 1, no un hero |
| Flujo | F-CMP — el núcleo es comparar 3 columnas, no una cola |
| Por qué no MARGEN | No S-TOP / H-GALLERY / F-STORY; no editorial; no lookbook |
| Por qué no CESTA | No S-RAIL / H-TABLE / F-LEAD; no ONG; no «otra cola» |
| Por qué no CANTERA | S-WIZ ≠ S-BOTTOM; F-CMP ≠ F-ONB; inmobiliaria ≠ parte de obra |
Terna unicidad
R-IN · S-WIZ · H-WIZARD · F-CMP
0 códigos iguales a N−1 (MARGEN). ≥2 distintos vs N−2 (CESTA) y N−3 (RONDA). Ventana 4 días tenía 2× R-CL (CESTA, MARGEN) → hoy R-IN.
Ground (CRON §5.6)
- Familia: G-CONCRETE
- Hex:
#E3E1DC - Por qué: hormigón fresco / reja. No es G-GALLERY
#FFFFFF(MARGEN), G-MOSS (CESTA), G-CHROME (RONDA), G-WATER (DERIVA) ni G-MUNICIPAL (PIZARRA). No es beige por inercia: el suelo es gris mineral frío.
Mood visual (Paper)
- Candidatos: mineral (primer instinto), rusted, industrial, chapel, nocturnal
- Elegido: rusted — no el primer instinto mineral (agencia caliza previsible)
- Paleta:
#E3E1DChormigón ·#1F1D1Bgrafito ·#B54A2Aóxido ·#6A6560hierro ·#F3F1ECcaliza (cards) ·#C9C4BBjunta - Tipo: Instrument Serif display · Sora UI (evita Cormorant / Public Sans / Spectral / Manrope / Zilla / Mulish / Newsreader / Outfit / Lora / Karla)
Alcance L1 must
- Wizard 3 pasos: zona+tipo → presupuesto+habitaciones → musts (ascensor, terraza, mascotas, reforma)
- Comparador 3 columnas con datos reales (precio, m², planta, gastos, extras)
- Ficha de finca
- Login + mesa AGENTE JWT (DRAFT → PUBLISHED)
- Alta de finca + success
- Empty (0 matches) / loading / error / mobile wizard
- ≥12 UX + ≥10 UI
Microcopy duro
- Comparar ≠ reservar. El comparador no pide ni confirma una visita.
- Criterio ≠ oferta. El presupuesto del wizard no es una oferta vinculante.
- Publicado ≠ visitado. La finca en mesa no implica que alguien haya entrado.
- ZAGUÁN no llama. La agencia llama; el producto no agenda slot (≠ F-BOOK / ≠ F-LEAD).
Supuestos
- S1: Una agencia de barrio lleva 8–20 fincas activas; la demo tiene 5 publicadas + 1 DRAFT.
- S2: Quien busca piso compara 2–3 anuncios en pestañas; no rellena un lead para «que le llamen».
- S3: La mesa es 1 agente; no un CRM de captación.
- S4: No hay pasarela, señal, ni contrato. El producto es criterio + comparación + catálogo.
Hipótesis
| ID | Señal |
|---|---|
| H1 | Un wizard de 3 pasos + 3 columnas reduce «abrir 6 tabs de Idealista» |
| H2 | Separar comparar de reservar evita la expectativa de cita confirmada |
| H3 | JWT solo en agente basta; quien busca no necesita cuenta |
00-paper-reference.md
Paper reference · ZAGUÁN
| URL | https://app.paper.design/file/01M09V13JEANPFFD6DS1RCN29G |
| File ID | 01M09V13JEANPFFD6DS1RCN29G |
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 Wizard 1 zona · UI-02 Wizard 2 presupuesto · UI-03 Wizard 3 musts · UI-04 Comparador 3 cols · UI-05 Ficha · UI-06 Login · UI-07 Mesa · UI-08 Alta · UI-09 Success · UI-10 Empty · UI-11 Error
UX process (prefijo UX- · mínimo 12)
| ID | Nombre | Contenido |
|---|---|---|
| UX-00 | Cover | Portada ZAGUÁN · eslogan «Tres fincas. Un criterio.» · terna R-IN · S-WIZ · H-WIZARD · F-CMP · L1 inmobiliaria de barrio · mood rusted · fecha 2026-08-18 |
| UX-01 | Stakeholders | BUSCADOR anónimo (Marcos), AGENTE (Lucía), PROPIETARIA (Inés), pareja, vecinos de Usera / Vallecas / Entrevías / Orcasur — no lead, no visita confirmada |
| UX-02 | Personas | Marcos Vidal (31, pareja, 2 hab Usera, tope 950 €) · Lucía Ferrer (46, agente, local Marcelo Usera) · Inés Roldán (58, propietaria del 3º de Amparo) |
| UX-03 | JTBD | Job «ver 3 fincas a la vez con mi criterio» + job «publicar el piso sin CRM» + job «que se compare el piso, no que se reserve online» |
| UX-04 | Stories | Must: wizard 3 pasos, comparador 3 cols, ficha, login AGENTE, mesa DRAFT→PUBLISHED, alta + success, empty/error, mobile wizard |
| UX-05 | Journey | Zona → presupuesto → musts → comparar 3 → abrir ficha → (AGENTE) login → mesa → nueva finca → publicar |
| UX-06 | Blueprint | Frontstage wizard/comparador/ficha · backstage llamada de Lucía e Inés con las llaves · sistemas Nest/Neon/JWT |
| UX-07 | Site map / IA | Público wizard-first + auth AGENTE; S-WIZ pasos, sin top-nav de marketing |
| UX-08 | Flujos | F-CMP: criterio → 3 columnas; F-DESK: login → lista → alta → PATCH PUBLISHED |
| UX-09 | Datos + permisos | User AGENTE · Listing · Comparison (opcional); JWT en GET mesa / POST / PATCH; público no ve DRAFT |
| UX-10 | Métricas | North star % sesiones que llegan a 3 columnas y abren ≥1 ficha; comparar no cuenta como visita |
| UX-11 | Research notes | Comprobado / supuesto / hipótesis / decisión — sin entrevistas de campo inventadas |
UI producto (prefijo UI- · mínimo 10)
| ID | Nombre | Flujo |
|---|---|---|
| UI-00 | Tokens | DS rusted / hormigón: hormigón, grafito, óxido, hierro, caliza, junta; Instrument Serif + Sora |
| UI-01 | Wizard 1 zona | H-WIZARD paso 1: zona + tipo (Usera / Vallecas / Entrevías / Orcasur · ALQUILER / VENTA) |
| UI-02 | Wizard 2 presupuesto | Paso 2: tope de renta + habitaciones. Criterio ≠ oferta |
| UI-03 | Wizard 3 musts | Paso 3: ascensor, terraza, mascotas, reforma |
| UI-04 | Comparador 3 cols | F-CMP: precio, m², planta, gastos, extras. Comparar ≠ reservar |
| UI-05 | Ficha | Finca: dirección, datos, foto, disclaimer «ZAGUÁN no llama» |
| UI-06 | Login | Acceso JWT AGENTE a la mesa |
| UI-07 | Mesa | Lista de fincas DRAFT / PUBLISHED + CTA nueva |
| UI-08 | Alta | Form de finca (dirección, zona, renta, musts, cover) |
| UI-09 | Success | Acuse de publicación + aviso «publicado ≠ visitado» |
| UI-10 | Empty | 0 matches al criterio / mesa vacía |
| UI-11 | Error | Fallo de red, reintentar |
Regla: wizard de N pasos → N boards UI solo para esos pasos, además de success/empty/error/loading/staff. ZAGUÁN es wizard: el núcleo es zona → presupuesto → musts → 3 columnas y mesa → alta → publicar.
Loading (skeletons de 3 columnas / hold de wizard) y mobile wizard (~390px, variante de UI-01…03) se especifican en docs 16 y 17; no ocupan un board extra del inventario de 12. [DECISIÓN]
Paper se construye en paralelo a esta suite. Los nombres de la tabla son el inventario planificado; el file ID ya existe.
1. Mapa de canvas (bandas)
| § | Banda | Propósito | Artboards clave |
|---|---|---|---|
| 1 | UX PROCESS | Modelo de servicio de inmobiliaria de barrio | UX-00…UX-11 |
| 2 | DESIGN SYSTEM | Tokens rusted + chrome de pasos + card caliza | UI-00 |
| 3 | PUBLIC WIZARD | Cara buscador: 3 pasos + comparador + ficha | UI-01, UI-02, UI-03, UI-04, UI-05, UI-10 |
| 4 | AGENTE | Flujos autenticados | UI-06, UI-07, UI-08, UI-09 |
| 5 | STATES | Resiliencia | UI-10, UI-11 |
Layout canvas (referencia de diseño): origen (0,0) · gaps ~100–120px · UX en 3×4 · UI pública en fila W1 → W2 → W3 → comparar → ficha · mesa en fila login → lista → alta → success · mobile wizard debajo de UI-01. [DECISIÓN] Bandas jerárquicas D-P0-04 / D-P1-04.
2. Mapeo Paper → Angular
| Artboard | Ruta app | Componente |
|---|---|---|
| UI-00 Tokens | — | tokens Tailwind + fuentes |
| UI-01 Wizard 1 zona | / | ZonaPage (paso 1) |
| UI-02 Wizard 2 presupuesto | /criterio/presupuesto | PresupuestoPage |
| UI-03 Wizard 3 musts | /criterio/musts | MustsPage |
| UI-04 Comparador | /comparar | CompararPage |
| UI-05 Ficha | /fincas/:slug | FincaPage |
| UI-06 Login | /login | LoginPage |
| UI-07 Mesa | /mesa | DeskPage |
| UI-08 Alta | /mesa/nueva | NewListingPage |
| UI-09 Success | /mesa/ok/:slug | OkPage |
| UI-10 Empty | /comparar 0 matches · /mesa 0 filas | empty accionable |
| UI-11 Error | wizard / comparar / mesa | banner error + retry |
[COMPROBADO] Rutas del encargo. No hay /visita, /reservar, /lead, search-first ni hero 2-col.
Loading y mobile no tienen artboard propio en el inventario de 12: el loading es skeleton sobre UI-04 / UI-01; el mobile es la misma ruta / a 390px. Ver docs 16 y 17.
3. Tokens de diseño en Paper
| Token | Valor | Uso |
|---|---|---|
| Hormigón / ground | #E3E1DC | Fondo de página, G-CONCRETE |
| Grafito | #1F1D1B | Texto fuerte, wordmark, cuerpo |
| Óxido | #B54A2A | Acento, progreso del wizard, PUBLISHED, CTA |
| Hierro | #6A6560 | Meta, labels, nav idle, planta, gastos |
| Caliza | #F3F1EC | Cards de finca / columnas (no el ground) |
| Junta | #C9C4BB | Bordes, rules, skeletons |
| Display | Instrument Serif 56/40/28 | Titulares, wordmark, dirección de finca |
| UI | Sora 16/14 | Body UI, labels, form, pasos, comparador |
[COMPROBADO] Hex y tipo salen de docs/00-day-brief.md. Mood elegido: rusted, no el primer instinto mineral (agencia caliza previsible).
4. Checklist de densidad (anti thin-frames)
| Criterio | UX | DS | Public wizard | AGENTE | States |
|---|---|---|---|---|---|
| Jerarquía tipográfica visible | Sí | Sí | Sí | Sí | Sí |
| Microcopy real (no lorem) | Sí | Sí | Sí | Sí | Sí |
| Tokens rusted aplicados | — | Sí | Sí | Sí | Sí |
| Datos de seed creíbles | Personas | — | 5 fincas + fotos | 1 DRAFT en mesa | Empty realista |
| Media / iconografía | Cover | — | Fotos de finca | Badges DRAFT/PUBLISHED | Skeletons |
| Chrome S-WIZ presente | — | Spec | UI-01, UI-02, UI-03 | — | — |
| Comparador 3 cols (no tabla, no feed) | — | Spec | UI-04 | — | — |
| Pasos del wizard visibles | — | Spec | UI-01…03 | — | — |
5. Media
assets/hero.jpg— atmósfera de zaguán / portal de finca de barrio, reja y hormigónassets/listings/amparo.jpg— Calle Amparo 14, 3º D, Usera (patio, sin ascensor)assets/listings/albufera.jpg— Av. de la Albufera 212, 5º, Vallecas (terraza, ascensor)assets/listings/yeserias.jpg— Paseo de Yeserías 8, bajo, Usera (mascotas OK)assets/listings/sicilia.jpg— Calle Sicilia 9, 2º, Entrevías (reforma, ascensor)assets/listings/alegria.jpg— Calle de la Alegría 3, ático, Orcasur (terraza, sin ascensor)
[COMPROBADO] Los seis archivos existen en el case 2026-08-18-zaguan/assets/. El DRAFT merced-2 no tiene foto pública.
6. Enlaces
- Paper: https://app.paper.design/file/01M09V13JEANPFFD6DS1RCN29G
- Case:
/Users/cristian/orca/ux-projects/2026-08-18-zaguan/ - App:
/Users/cristian/orca/zaguan-app/ - GitHub: https://github.com/Criscode2022/zaguan-app
- Neon project:
curly-recipe-23023272 - API:
http://localhost:3018 - Web:
http://localhost:4200
01-project-definition.md
01 — Definición de proyecto — ZAGUÁN
1. Identidad
| Campo | Valor |
|---|---|
| Nombre | ZAGUÁN |
| Significado | El zaguán es el umbral del edificio de barrio: reja, hormigón, buzones. Ni el portal de anuncios ni el hall de una agencia de centro. Se entra con un criterio; se salen tres fincas. |
| Eslogan | ”Tres fincas. Un criterio.” |
| Una frase | Wizard de criterios + comparador de 3 fincas + ficha + mesa AGENTE JWT. |
| Sector | Inmobiliaria de barrio (Usera / Vallecas / Entrevías / Orcasur) |
| Tipo | Web L1 — wizard + comparador 3 cols + ficha + mesa AGENTE — Nivel 1, configurador usable |
| Plataforma | Web responsive (wizard/comparador desktop · pasos mobile · mesa agente) |
| Mercado demo | España · Madrid · Usera / Vallecas / Entrevías / Orcasur (una sola agencia, no red multi-distrito) |
| Idioma | es-ES |
| Fecha caso | 2026-08-18 |
| Terna | R-IN · S-WIZ · H-WIZARD · F-CMP |
[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] Quien busca piso en el sur de Madrid no rellena un lead para «que le llamen»: abre seis pestañas de Idealista y compara de memoria. El presupuesto se lee como oferta, el anuncio publicado se lee como «ya lo he visitado» y el botón «pedir visita» se lee como cita confirmada. [SUPUESTO] Una agencia de barrio lleva 8–20 fincas activas; no opera un CRM de captación ni una pasarela de señal.
Secundarios
| Problema | Quién lo sufre | Efecto |
|---|---|---|
| Seis pestañas, cero criterio escrito | BUSCADOR (Marcos) | Elige por foto, se pasa de tope, vuelve a empezar |
| El tope de renta se lee como oferta | Ambos | «Has ofertado 950» cuando solo filtró |
| Publicado se lee como visitado | AGENTE (Lucía) + propietaria | Inés pregunta «¿ya ha venido alguien?» |
| Comparar se lee como reservar | Propietaria (Inés) + Marcos | Espera una llamada que el producto no hace |
| Mesa de fincas = WhatsApp + Excel | AGENTE | El DRAFT se cuela en el escaparate |
Supuestos (no investigación primaria propia)
- S1: Una agencia de barrio lleva 8–20 fincas activas; la demo tiene 5 publicadas + 1 DRAFT. [SUPUESTO]
- S2: Quien busca piso compara 2–3 anuncios en pestañas; no rellena un lead para «que le llamen». [SUPUESTO]
- S3: La mesa es 1 agente; no un CRM de captación. [SUPUESTO]
- S4: No hay pasarela, señal, ni contrato. El producto es criterio + comparación + catálogo. [SUPUESTO]
Hipótesis de producto
| ID | Hipótesis | Señal de validación (futura) |
|---|---|---|
| H1 | Un wizard de 3 pasos + 3 columnas reduce «abrir 6 tabs de Idealista» | % sesiones que llegan a /comparar con 3 cols |
| H2 | Separar comparar de reservar evita la expectativa de cita confirmada | ↓ «pensé que ya tenía visita» |
| H3 | JWT solo en agente basta; quien busca no necesita cuenta | 0 cuentas de buscador; comparación completa sin login |
No se afirman estadísticas de portales inmobiliarios inventadas. Todo lo anterior es razonamiento de diseño etiquetado.
3. Propuesta de valor
| Para | Valor |
|---|---|
| BUSCADOR (Marcos Vidal) | Deja el criterio en 3 pasos; ve 3 fincas a la vez; abre la ficha sin crear cuenta. |
| AGENTE (Lucía Ferrer) | Lista las fincas, da de alta una nueva, publica DRAFT → PUBLISHED sin un CRM de 40 campos. |
| PROPIETARIA (Inés Roldán) | El 3º de Amparo se compara en un sitio; no convierte ZAGUÁN en agenda de visitas. |
| Barrio / agencia | Canal mínimo: criterio visible + comparación + cierre de catálogo, sin lead ni señal. |
No es ZAGUÁN
| Excluido | Por qué |
|---|---|
| Lead / «te llamamos» / cuenta buscador | S2 + H3: llega a comparar, no a dejar el móvil |
| Reserva / confirmación de visita | Comparar ≠ reservar (≠ F-BOOK / ≠ F-LEAD) |
| Semanario / lookbook de crónicas | Eso fue MARGEN (H-GALLERY / F-STORY) |
| Tabla de necesidades / compromiso de caja | Eso fue CESTA (H-TABLE / F-LEAD) |
| Parte de obra / wizard de oficio | Eso fue CANTERA (S-BOTTOM / F-ONB) |
| Feed / cronología de «pisos nuevos» | Terna H-WIZARD, no H-FEED |
| Search-first / catálogo facetado como home | Terna no es H-SEARCH; la home es el paso 1 |
| Pasarela, señal, contrato, hipoteca | S4 |
| Hero 2-col + 3 cards de «cómo buscamos» | Anti-patrón AP-12; contradice H-WIZARD |
4. Objetivos
Negocio / caso de estudio
- Demostrar vertical slice L1 inmobiliario wizard + comparador con Listing + JWT AGENTE + Comparison opcional.
- Portfolio coherente: Paper (12 UX + 12 UI) + docs + app runnable.
- Terna R-IN · S-WIZ · H-WIZARD · F-CMP frente a MARGEN (galería/masthead), CESTA (tabla/rail) y CANTERA (dock de parte).
Usuario
| Rol | Objetivo medible en demo |
|---|---|
| BUSCADOR | Llegar a 3 columnas en < 45 s; abrir una ficha en 1 tap más |
| AGENTE | Publicar una finca DRAFT → PUBLISHED en < 2 min desde la mesa |
| PROPIETARIA | Ver su finca en el comparador sin encontrar un CTA de reserva |
No objetivos v1 (explícitos)
- Lead, teléfono del buscador, «te llamamos»
- Reserva de visita / calendario / slot
- Cuenta de buscador, favoritos, alertas
- Pasarela, señal, contrato, scoring hipotecario
- Multi-agente, comisiones, captación
- Mapa de fincas, street view, tour 3D
- Portal multi-agencia / scrap de Idealista
- App nativa ni notificaciones push
5. Roles y permisos (resumen)
| Acción | Público (BUSCADOR / PROPIETARIA) | AGENTE |
|---|---|---|
| Completar wizard de criterio | Sí | Sí |
| Ver comparador (PUBLISHED) | Sí | Sí |
| Leer ficha PUBLISHED | Sí | Sí |
| Ver finca DRAFT | No (404) | Sí (mesa) |
| Pedir / confirmar visita | No (no existe) | No (fuera de app) |
| Login JWT | No (no cuenta buscador) | Sí |
| Listar fincas de mesa | No (401) | Sí |
| Crear finca | No | Sí |
| Publicar / despublicar | No | Sí |
| Persistir Comparison | Implícito en POST compare (opcional) | Lectura Could L+1 |
[DECISIÓN] Un solo rol autenticado: AGENTE. El buscador es anónimo. La propietaria no es un rol de sistema: es persona / stakeholder que usa la cara pública (o habla con Lucía fuera).
6. Métricas (modelo, no instrumentadas en v1 salvo base)
| Tipo | Métrica | Definición |
|---|---|---|
| North Star | % de sesiones que llegan a /comparar con 3 columnas y abren ≥1 ficha | Completud de criterio + comparación, no pageviews |
| Activación buscador | Completar los 3 pasos del wizard | H1 |
| Activación agente | 1.er PATCH a PUBLISHED | Cierre de catálogo |
| Expectativa | Reclamaciones «ya tenía visita» / «habéis llamado» / «he ofertado» | H2 + microcopy |
| Integridad | GET público no devuelve DRAFT | Publicado ≠ visitado (y borrador invisible) |
| Salud | Error rate API listings/compare | 4xx/5xx |
7. Alcance funcional v1 (L1)
| Módulo | Incluido |
|---|---|
| Wizard 3 pasos | Zona+tipo → presupuesto+habitaciones → musts (ascensor, terraza, mascotas, reforma) |
| Comparador | 3 columnas con precio, m², planta, gastos, extras |
| Ficha de finca | Dirección, datos, foto, disclaimer |
| Auth | POST /api/auth/login → JWT AGENTE |
| Mesa | GET lista DRAFT + PUBLISHED, PATCH :id { status } |
| Alta | Form + POST /api/listings + success |
| Estados UI | Empty (0 matches), loading skeleton, error de red |
| Mobile wizard | Mismos 3 pasos a ~390px |
| Seed | 1 AGENTE, 5 fincas PUBLISHED, 1 DRAFT |
8. Criterios de aceptación de producto
- Un visitante puede completar el wizard sin autenticación y ver hasta 3 fincas comparadas.
- Un visitante puede abrir una ficha sin cuenta y sin encontrar un CTA de reserva o «te llamamos».
- La UI dice explícitamente que comparar no es reservar, que el criterio no es oferta, que publicado no es visitado y que ZAGUÁN no llama.
- Sin token, GET
/api/mesa/listingsy POST/PATCH de listings responden 401. - GET
/api/listingsdevuelve solo PUBLISHED; el DRAFTmerced-2no aparece. - GET
/api/listings/:slugde un DRAFT responde 404 (no 200 + aviso). - Un AGENTE puede iniciar sesión y ver la mesa con DRAFT y PUBLISHED.
- El AGENTE puede crear una finca y publicarla (
DRAFT→PUBLISHED). - La home es el paso 1 del wizard: no hay hero 2-col + 3 cards como superficie principal.
- Fotos reales en las 5 fincas publicadas (assets del case).
- El comparador muestra precio, m², planta, gastos, extras en 3 columnas.
- Rutas públicas y de mesa solo las del doc 03.
9. Stack y artefactos
| Capa | Detalle |
|---|---|
| Frontend | Angular + Tailwind · puerto 4200 |
| Backend | NestJS · puerto 3018 |
| DB | Neon PostgreSQL · project curly-recipe-23023272 |
| Auth | JWT (AGENTE) |
| Diseño | Paper 01M09V13JEANPFFD6DS1RCN29G |
| Repo app | /Users/cristian/orca/zaguan-app/ · GitHub Criscode2022/zaguan-app |
[COMPROBADO] Puerto, Neon, Paper file ID, repo y path de app constan en el encargo.
10. Riesgos y mitigaciones
| Riesgo | Impacto | Mitigación v1 |
|---|---|---|
| Expectativa de visita confirmada | Reclamaciones «ya tenía cita» | Copy «Comparar ≠ reservar»; sin botón Reservar |
| Expectativa de oferta vinculante | «Has ofertado 950» | Copy «Criterio ≠ oferta» en paso 2 y comparador |
| Publicado se lee como visitado | Inés pregunta por visitas | Copy «Publicado ≠ visitado» en success y mesa |
| El producto «llama» | Expectativa F-BOOK | «ZAGUÁN no llama. La agencia llama.» |
| DRAFT filtrado a comparar | Finca a medias pública | GET público filtra status=PUBLISHED; slug DRAFT → 404 |
| Home percibida como landing de agencia | Pérdida de craft / H-WIZARD | Paso 1 es la home; no hero 2-col |
| Confundir con MARGEN (galería editorial) | Expectativa de crónica | No hay masthead de periódico ni agenda |
| Confundir con CANTERA (parte de obra) | Expectativa de «enviar el día» | F-CMP, no F-ONB; 3 columnas, no parte |
Leftover pieces / issues / events en repo | App enseña otro producto | Contrato de esta suite gana; I01 de build |
| PII de buscador | No aplica v1 | Buscador no deja datos; AGENTE es la única cuenta |
| 0 matches al criterio | Abandono | Empty «ninguna finca encaja; afloja un must» |
11. Glosario
| Término | Definición en ZAGUÁN |
|---|---|
| Criterio | Conjunto zona + tipo + tope + hab + musts; no es una oferta |
| Comparador | 3 columnas de fincas; no es una reserva |
| Finca / listing | listings: anuncio de piso en alquiler (demo) |
| Must | Filtro duro o blando: ascensor, terraza, mascotas, reforma |
| Mesa | Superficie AGENTE: lista + alta + publicar |
| Publicar | Pasar listing a PUBLISHED; no significa que alguien haya visitado |
| Borrador | DRAFT; el barrio no lo ve |
| BUSCADOR | Visitante anónimo que compara |
| AGENTE | Usuario autenticado que opera la mesa |
| Caliza | Superficie de card #F3F1EC, no el ground |
| Junta | Rule #C9C4BB entre columnas y campos |
| Chrome de pasos | Shell S-WIZ: wordmark + 1 / 2 / 3, sin top-nav de marketing |
| Zaguán | Umbral: se entra con criterio, se salen tres fincas |
12. Decisiones de diseño (cierre de brief)
| ID | Decisión | Alternativa descartada |
|---|---|---|
| D1 | Home = paso 1 del wizard (H-WIZARD) | Hero 2-col + 3 cards (AP-12) · galería (MARGEN) · tabla (CESTA) · search (PIZARRA) |
| D2 | Shell chrome de pasos (S-WIZ) | Masthead de periódico · rail de iconos · dock flotante · sidebar CRM |
| D3 | Registro industrial rusted (R-IN) | Mineral / agencia caliza (primer instinto) · gallery · botanical |
| D4 | Flujo F-CMP (criterio → 3 columnas) | F-STORY, F-LEAD, F-ONB, F-BOOK, F-SEARCH |
| D5 | Mood rusted | Mineral (vetado como primer instinto), gallery MARGEN, moss CESTA |
| D6 | Un rol autenticado AGENTE | Multi-rol buscador / propietaria / admin / captador |
| D7 | Estados solo DRAFT / PUBLISHED | RESERVED / VISITED / OPTIONED (CRM inmobiliario) |
| D8 | Comparador de solo lectura | Form de visita, calendario, «te llamamos» |
| D9 | Buscador sin cuenta | Lead, alerta, favoritos obligatorios |
| D10 | PATCH /api/listings/:id { status } | Subruta /publish como único contrato |
| D11 | Ground G-CONCRETE #E3E1DC | G-GALLERY, G-MOSS, G-CHROME, G-WATER, G-MUNICIPAL |
| D12 | Success de publicación en /mesa/ok/:slug | Toast único sin pantalla |
| D13 | tipo = ALQUILER | VENTA | Tipo de inmueble (ático/bajo) vive en extras / planta |
| D14 | Comparison persistida de forma opcional | Obligar cuenta para comparar |
13. Relación con el día anterior
| Case | Terna | Por qué ZAGUÁN no lo copia |
|---|---|---|
| MARGEN (N−1) | S-TOP / H-GALLERY / F-STORY | No editorial, no lookbook, no masthead, no agenda |
| CESTA (N−2) | S-RAIL / H-TABLE / F-LEAD | No ONG, no tabla de necesidades, no compromiso, no rail |
| RONDA (N−3) | S-DOCK / H-FEED / F-BOOK | No gaming, no cronología, no dock, no sillas |
| CANTERA | S-BOTTOM / F-ONB | S-WIZ ≠ S-BOTTOM; F-CMP ≠ F-ONB; inmobiliaria ≠ parte de obra |
[COMPROBADO] El brief fija terna R-IN · S-WIZ · H-WIZARD · F-CMP. 0 códigos iguales a N−1 (MARGEN). ≥2 distintos vs N−2 (CESTA) y N−3 (RONDA). Ventana 4 días tenía 2× R-CL (CESTA, MARGEN) → hoy R-IN. Ground G-CONCRETE #E3E1DC, no G-GALLERY.
02-ux-research-strategy.md
02 — Estrategia de investigación UX — ZAGUÁN
Importante: este documento contiene artefactos de diseño y razonamiento secundario.
No hay entrevistas de campo primarias ni estadísticas inventadas presentadas como dato medido.
Etiquetas: [COMPROBADO] en producto, [SUPUESTO], [HIPÓTESIS], [DECISIÓN].
1. Objetivos de investigación (del caso)
| Objetivo | Método en este caso | Salida |
|---|---|---|
| Entender actores de la agencia de barrio | Modelado de stakeholders + personas | §3–4 |
| Definir job de “ver 3 fincas con mi criterio” y de publicar | JTBD + stories Must | §5 |
| Mapear fricción Idealista / WhatsApp → comparación | Journey + service blueprint | §6–7 |
| Traducir a requisitos L1 wizard + comparador | Matriz hallazgo → requisito → feature | §8 |
2. Fuentes y límites
Fuentes admisibles (secundarias / operativas)
- Conocimiento general de búsqueda de alquiler en Madrid sur, portales (Idealista / Fotocasa) y agencias de barrio con escaparate en Marcelo Usera.
- Analogía operativa con wizard de criterios + mesa JWT de la serie daily.
- Restricciones ALS-2: no lead, no reserva, no galería editorial, no tabla de necesidades, no parte de obra.
Límites éticos de verdad
| Prohibido | Permitido |
|---|---|
| “El 68% de buscadores de Usera abre 6 tabs” sin fuente | “[SUPUESTO] el buscador llega con ‘quiero ver tres a la vez’” |
| Citas de entrevistas ficticias como campo real | Quotes de persona etiquetadas como constructo de diseño |
| Precio medio medido “890 € el 3º de Amparo” como KPI de mercado | Renta de seed de producto, etiquetada como demo |
| Horario oficial de una agencia concreta en Marcelo Usera | Local de seed; Lucía es persona de diseño |
| “Lucía existe y nos dijo…” | Lucía Ferrer es persona de diseño |
3. Stakeholders
| Stakeholder | Influencia | Interés | Necesidad principal |
|---|---|---|---|
| BUSCADOR / pareja (Marcos) | Baja formal | Muy alta | Dejar criterio y ver 3 fincas sin cuenta |
| AGENTE / agencia (Lucía) | Alta | Muy alta | Publicar fincas sin CRM enterprise |
| PROPIETARIA (Inés) | Media–alta | Alta | Que el 3º de Amparo se compare; no gestionar visitas aquí |
| Pareja de Marcos | Indirecta | Alta | Que el tope de 950 € se respete en las 3 columnas |
| Vecindario (Usera / Vallecas / Entrevías / Orcasur) | Baja v1 | Media | Aparecer como zona, no como “sur genérico” |
| Portal (Idealista / Fotocasa) | Competencia / canal paralelo | — | No clonar el portal; sí sustituir las 6 pestañas |
| Captador / “te llamamos” | Baja v1 | Baja | Fuera: comparar ≠ lead |
Mapa de poder (resumen)
- Decisor de adopción: la agente (quien publica el escaparate) y, en difusión, el boca a boca del local.
- Usuario frecuente de comparación: BUSCADOR (wizard + 3 columnas + ficha).
- Usuario frecuente de publicación: AGENTE (mesa).
- Riesgo de rechazo: si la home no es el paso 1; si el comparador parece un formulario de visita; si un DRAFT se filtra.
4. Personas
P1 — Marcos Vidal · BUSCADOR
| Campo | Detalle |
|---|---|
| Edad / contexto | 31 años; pareja; busca 2 hab en Usera; tope 950 €; el móvil es el escaparate |
| Digital | Media–alta; tiene Idealista en 6 pestañas; odia el “te llamamos en 5 minutos” |
| Goals | Dejar zona y tope, ver 3 fincas a la vez, abrir la que encaje, no crear cuenta |
| Pains | Se pasa de presupuesto sin darse cuenta; no sabe si hay ascensor hasta el quinto anuncio; le llaman por dejar el móvil |
| Quote de diseño | “Quiero ver tres a la vez. No me llaméis.” |
| Seed demo | Sin email de producto; llega por enlace o por el wordmark |
Escenario: Abre ZAGUÁN, elige Usera + ALQUILER, tope 950 €, 2 hab, no exige ascensor, compara Amparo 14 y Yeserías 8, abre la ficha del 3º. No se registra.
[DECISIÓN] Marcos no tiene cuenta. Crear usuario de buscador sería fricción y contradice H3 / S2.
P2 — Lucía Ferrer · AGENTE
| Campo | Detalle |
|---|---|
| Edad / contexto | 46 años; agente de barrio; local en Marcelo Usera; lleva 8–20 fincas |
| Digital | Media–alta; desktop para alta; móvil para comprobar el comparador publicado |
| Goals | Ver el estado de cada finca, dar de alta una nueva, publicar sin “captar lead” |
| Pains | WhatsApp + Excel + el portal; miedo a publicar un borrador; Inés pregunta si “ya ha venido alguien” |
| Quote de diseño | “Si está en DRAFT, el barrio no lo ve. Publicado no significa que nadie haya subido.” |
| Email demo | lucia@zaguan.barrio / password123 |
Escenario: Login → mesa → ve Calle de la Merced 2 en DRAFT → termina → Publicar → success con aviso de que publicado no es visitado.
[COMPROBADO] Credencial demo sale del encargo. Nombre, edad y local son constructo de diseño. [DECISIÓN]
P3 — Inés Roldán · PROPIETARIA
| Campo | Detalle |
|---|---|
| Edad / contexto | 58 años; propietaria del 3º D de Calle Amparo 14; vive aún allí / lo alquila [SUPUESTO de escenario] |
| Digital | Media; WhatsApp con Lucía; desconfía de “pide visita online” |
| Goals | Que el piso se compare con otros dos; que la visita la dé ella cuando Lucía llame |
| Pains | Cada anuncio se convierte en “¿a qué hora vengo?”; el portal enseña el piso al lado de 40 de Chamberí |
| Quote de diseño | “Que se compare el piso. La visita la doy yo cuando Lucía llame.” |
Escenario: Abre el comparador (o se lo enseña Lucía), ve Amparo 14 al lado de Yeserías 8. No hay botón Reservar.
[DECISIÓN] Inés no es rol de sistema. No hay login PROPIETARIA en L1. Evita convertir ZAGUÁN en CRM de captación.
Anti-personas
| Quién | Por qué no es target v1 |
|---|---|
| Inversor que quiere 40 fincas y yield | S1: 8–20 activas; no es portal |
| Buscador que deja el móvil “para que le llamen” | S2 / H2: no hay lead |
| Community manager de portal con likes | S4; rompe F-CMP |
| Hipotecado que quiere simulador y tasación | S4 |
| Jefe de producto de CRM inmobiliario (40 estados) | S3: mesa de 1 agente |
| Lectora de MARGEN que pregunta “qué hay esta semana” | Otro job, otro día |
5. JTBD y user stories
Job principal (BUSCADOR)
Cuando busco piso en el barrio y ya tengo tope y zona,
quiero dejar el criterio y ver tres fincas a la vez,
para no abrir seis pestañas ni dejar el móvil a un captador.
Job principal (AGENTE)
Cuando entra un piso o termino las fotos,
quiero listar las fincas, dar de alta la que falta y publicarla,
para que el barrio vea solo lo cerrado y yo no confunda borrador con escaparate.
Job principal (PROPIETARIA)
Cuando mi piso está en alquiler,
quiero que se compare con otros dos del barrio,
para que se entienda el precio sin que ZAGUÁN reserve la visita.
Jobs secundarios
| Job | Rol |
|---|---|
| Distinguir publicado de borrador | Ambos |
| Ver planta, gastos y extras en la misma fila visual | BUSCADOR |
| Saber que el tope no es una oferta | BUSCADOR |
| Publicar sin confirmar una visita | AGENTE |
| Ocultar un borrador al comparador | AGENTE |
| Entender que nadie ha subido aún | PROPIETARIA |
Stories Must (v1)
| ID | Story | AC |
|---|---|---|
| US1 | Como buscador, quiero elegir zona y tipo | Paso 1 /; chips USERA / VALLECAS / ENTREVIAS / ORCASUR |
| US2 | Como buscador, quiero fijar tope y habitaciones | Paso 2; copy «criterio ≠ oferta» |
| US3 | Como buscador, quiero marcar musts | Paso 3: ascensor, terraza, mascotas, reforma |
| US4 | Como buscador, quiero ver 3 fincas comparadas | POST /api/compare → 3 cols; sin CTA de reserva |
| US5 | Como buscador, quiero abrir la ficha | GET :slug; foto real; disclaimer «no llama» |
| US6 | Como buscador, no quiero crear cuenta | Cero forms de registro en cara pública |
| US7 | Como AGENTE, quiero entrar con email/password | JWT + redirect /mesa |
| US8 | Como AGENTE, quiero listar DRAFT y PUBLISHED | GET /api/mesa/listings JWT |
| US9 | Como AGENTE, quiero crear una finca | POST /api/listings → DRAFT |
| US10 | Como AGENTE, quiero publicarla | PATCH { status: "PUBLISHED" } + success |
| US11 | Como cualquiera, quiero empty / error | UI-10, UI-11 |
| US12 | Como buscador en móvil, quiero hacer el wizard a ~390px | Variante UI-01; cuerpo ≥16px; targets ≥44px |
MoSCoW (v1 L1)
| Prioridad | Ítems |
|---|---|
| Must | Wizard 3 pasos, comparador 3 cols, ficha, login, mesa, alta, success, empty/error, mobile wizard |
| Should | Badge DRAFT/PUBLISHED en mesa; pie «ZAGUÁN no llama»; mismatch badges en columnas |
| Could | Persistir Comparison; despublicar a DRAFT; ordenar mesa por updatedAt; loading skeleton |
| Won’t | Lead, visita, pasarela, multi-agente, mapa, cuenta buscador |
6. Journey (BUSCADOR → AGENTE)
| Fase | Actor | Acción | Touchpoint | Emoción [HIPÓTESIS] |
|---|---|---|---|---|
| 1 Entrar | Marcos | Aterriza; el paso 1 es la home | / chrome S-WIZ | Claridad, “esto no es un portal” |
| 2 Acotar | Marcos | Usera + ALQUILER → 950 €, 2 hab → musts sueltos | /criterio/presupuesto · /criterio/musts | Control (el tope no es oferta) |
| 3 Comparar | Marcos | Ve Amparo 14 · Yeserías 8 · (tercera) | /comparar 3 cols | Alivio de no abrir 6 tabs |
| 4 Mirar | Marcos | Abre ficha del 3º de Amparo | /fincas/amparo-14 | Reconocimiento del patio |
| 5 No reservar | Marcos | No hay botón; anota la dirección | Fuera de app | Control (H2) |
| 6 Publicar | Lucía | Login, ve DRAFT Merced 2, publica | /mesa → /mesa/nueva → ok | Oficio, no euforia de “ya hay visita” |
| 7 Comprobar | Lucía | Abre comparador en incógnito: el DRAFT no está | / pública | Confianza |
Momentos de verdad
- Paso 1 en el primer viewport — si hay que “descubrir la marca”, se rompe H-WIZARD. [HIPÓTESIS]
- Comparador sin botón de reserva — si aparece “Pedir visita”, H2 falla.
- DRAFT invisible en público — prueba de integridad de catálogo.
- Success de mesa dice que publicado no es visitado — evita la mentira de la visita.
- Tope etiquetado como criterio — si el paso 2 dice “tu oferta”, H2/microcopy fallan. [HIPÓTESIS]
7. Service blueprint (resumen)
| Capa | Elementos |
|---|---|
| Frontstage BUSCADOR | Chrome de pasos, wizard 1–3, comparador, ficha |
| Frontstage AGENTE | Login, mesa, alta, success |
| Backstage | Llamada de Lucía a Inés, juego de llaves, visita en el zaguán (fuera de app) |
| Sistemas | Nest API :3018, Neon curly-recipe-23023272, JWT, Angular wizard |
| Soportes | Seed 1 AGENTE + 5 PUBLISHED + 1 DRAFT + fotos, Paper, docs |
| Fallos | 0 matches; 401 sin token; 404 slug DRAFT; red caída → UI error |
Fallos de servicio y respuesta de diseño
| Fallo | Evidencia de UI | Recuperación |
|---|---|---|
| 0 fincas al criterio | UI-10 Empty comparador | “Ninguna finca encaja con este criterio. Afloja un must o sube el tope.” |
| Mesa vacía | UI-10 Empty mesa | “Aún no hay fincas. Da de alta la primera.” |
| API caída | UI-11 Error | Reintentar |
| Latencia compare | Loading (doc 16) | Skeleton de 3 columnas caliza |
| Form inválido | Inline field | No navegar a success ni a comparar |
| Intento de leer DRAFT por URL | 404 | “Esa finca no está publicada.” [H3 / integridad] |
| Paso 2 sin zona | Bloqueo | Volver a / ; no se salta el wizard |
8. Matriz hallazgo → requisito → feature
| Hallazgo | Tipo | Requisito | Feature v1 |
|---|---|---|---|
| Llega con “quiero ver tres a la vez” | [SUPUESTO] S2 | Wizard es la home | UI-01 / H-WIZARD |
| Seis pestañas no escriben el criterio | [HIPÓTESIS] H1 | 3 pasos + 3 columnas | POST compare |
| Comparar se lee como reservar | [HIPÓTESIS] H2 | Cero CTA de visita | UI-04 / UI-05 solo lectura |
| Tope se lee como oferta | [DECISIÓN] | Copy irrenunciable | UI-02 + comparador |
| Borrador se filtra | [SUPUESTO] | Público = PUBLISHED | 404 en DRAFT |
| Publicado ≠ visitado | [DECISIÓN] | Copy irrenunciable | Success + mesa |
| Agencia 1 persona | [SUPUESTO] S3 | Mesa = lista, no CRM | UI-07 / UI-08 |
| Buscador no necesita cuenta | [HIPÓTESIS] H3 | Auth solo AGENTE | Login + guards API |
| L1 compacto, no portal | [DECISIÓN] | Un rol AGENTE; 5+1 seed | Sin cuenta BUSCADOR, sin mapa |
9. Preguntas abiertas (no bloquean v1)
| ID | Pregunta | Cómo se resolvería después |
|---|---|---|
| Q1 | ¿Comparison se persiste siempre o solo si el POST lo pide? | [DECISIÓN v1] persistencia opcional; el contrato devuelve { criteria, listings[3] } |
| Q2 | ¿Musts son filtros duros o score blando? | [DECISIÓN v1] duros si están ON; si 0 matches, empty. Could: relajar y marcar mismatch |
| Q3 | ¿VENTA en el seed? | No. El chip existe; empty honesto si se elige VENTA |
| Q4 | ¿Despublicar vuelve a DRAFT o hay UNLISTED? | [DECISIÓN v1] PATCH a DRAFT basta |
| Q5 | ¿Gastos comunitarios son oficiales de cada finca? | No. Seed demo. [SUPUESTO de catálogo] |
| Q6 | ¿El local de Marcelo Usera es una agencia real? | No. Atrezzo de persona. No se afirma como negocio existente |
10. Plan de research futuro (si hubiera campo real)
| Método | Muestra orientativa | Pregunta |
|---|---|---|
| Test de usabilidad wizard | 5–6 buscadores 25–40 | ¿Llegan a 3 columnas en < 45 s? |
| Shadowing de mesa | 1 agente de barrio | ¿La mesa sustituye Excel + WhatsApp? |
| Revisión de expectativa de visita | Clics en comparar vs “¿cuándo subo?” | Validar H2 |
| Wizard mobile 390 | 5 sesiones | ¿Los 3 pasos se completan con el pulgar? |
Estos métodos no se han ejecutado. No se reportan hallazgos como si lo hubieran sido.
11. Síntesis
ZAGUÁN se diseña como configurador de barrio que compara y publica, no como portal de leads, no como agenda de visitas y no como lookbook editorial.
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 — ZAGUÁN
1. Principios de IA
| Principio | Aplicación |
|---|---|
| Wizard first | La home es el paso 1. No hay landing de marca por delante. |
| Criterio → 3 columnas → ficha | Profundidad 2 desde la columna hasta la finca; no hay modal de reserva |
| Público vs AGENTE | Wizard, comparador y ficha abiertos; mesa solo autenticada |
| Lenguaje de dominio | Criterio, finca, must, mesa, zaguán — no “lead”, “SKU”, “pieza”, “plaza” |
| Borrador invisible | El barrio no ve DRAFT; 404 si adivina el slug |
| Comparar se mira | Columna no tiene cupo, botón ni form de visita (≠ F-BOOK) |
| Chrome de pasos siempre a mano | S-WIZ: wordmark + 1 / 2 / 3; no masthead, no rail, no dock |
2. Sitemap
/ Wizard paso 1 · zona + tipo (público) H-WIZARD
/criterio/presupuesto Wizard paso 2 · tope + habitaciones
/criterio/musts Wizard paso 3 · ascensor / terraza / mascotas / reforma
/comparar Comparador 3 columnas (público)
/fincas/:slug Ficha de finca (público si PUBLISHED)
/login Login JWT AGENTE
/mesa Lista de fincas (auth AGENTE)
/mesa/nueva Alta de finca (auth AGENTE)
/mesa/ok/:slug Success post-publicación (auth AGENTE)
/** → redirect /
Árbol por audiencia
| Audiencia | Nodos relevantes |
|---|---|
| BUSCADOR / PROPIETARIA | Zona → Presupuesto → Musts → Comparar → Ficha |
| AGENTE | Login → Mesa → Nueva → Ok (y wizard público para comprobar) |
| Ambos | Wordmark → / (AGENTE autenticada: enlace discreto Mesa) |
[COMPROBADO] Nueve rutas del encargo L1. No se inventan /visita, /reservar, /lead, /favoritos, /mesa/:id.
3. Navegación
Chrome de pasos (S-WIZ)
| Slot | Destino | Visibilidad | Notas |
|---|---|---|---|
| Wordmark ZAGUÁN | / (reinicia criterio) | Todos | Instrument Serif; no logotipo ilustrado |
| 1 Zona | / | Todos en wizard | Estado activo en paso 1 |
| 2 Presupuesto | /criterio/presupuesto | Si hay zona | Bloqueado si falta paso 1 |
| 3 Musts | /criterio/musts | Si hay tope | Bloqueado si falta paso 2 |
| Soy agente / Mesa | /login o /mesa | Pie discreto, no masthead | Acceso AGENTE |
[DECISIÓN] El chrome de pasos es la única navegación primaria de la cara pública. Encaja L1 compacto. No hay masthead (MARGEN), no rail (CESTA), no dock (RONDA / CANTERA).
Desktop ≥960px: wordmark a la izquierda; pasos 1—2—3 a la derecha o centrados; rule junta bajo el chrome; óxido en el paso activo.
Mobile ~390px: wordmark + “1 de 3”; los labels Zona / Presupuesto / Musts se compactan. [DECISIÓN] En 390 no se introduce hamburger de marketing.
Pública (además del chrome)
| Elemento | Destino | Notas |
|---|---|---|
| Chip de zona | estado del paso 1 | Usera · Vallecas · Entrevías · Orcasur |
| Siguiente | paso N+1 | Primario óxido |
| Atrás | paso N−1 | Secundario |
| Columna de finca | /fincas/:slug | Foto + dirección + datos |
| Disclaimer | no navega | Texto |
| “Soy agente” | /login | Pie, Sora 13 hierro |
[DECISIÓN] No hay /fincas índice. El catálogo se compara, no se lista. Un listado facetado sería H-SEARCH / L+1.
AGENTE (autenticado)
| Elemento | Destino | Visibilidad |
|---|---|---|
| Mesa | /mesa | AGENTE |
| Nueva finca | /mesa/nueva | AGENTE |
| Badge rol | — | AGENTE |
| Nombre | Lucía Ferrer | AGENTE |
| Salir | limpia token → /login | AGENTE |
| Publicar | en form o en fila de mesa | AGENTE · no hay /mesa/:id de ficha CRM |
[DECISIÓN] L1: el PATCH vive en la fila de la mesa y/o al final del form de alta. No se añade /mesa/:id como Must.
En mesa el chrome de pasos desaparece: la trastienda no finge un wizard. Wordmark + Mesa + Salir bastan. [DECISIÓN]
4. Inventario de contenido
| Pantalla | Contenidos |
|---|---|
Paso 1 / | Chrome S-WIZ, titular, chips de zona, tipo ALQUILER/VENTA, Siguiente, empty de estado, “Soy agente” |
| Paso 2 | Tope de renta (input o slider), habitaciones (1 / 2 / 3+), disclaimer «criterio ≠ oferta», Atrás / Siguiente |
| Paso 3 | Toggles ascensor, terraza, mascotas, reforma, Atrás / Comparar |
| Comparador | Criterio resumen, 3 columnas (foto, dirección, precio, m², planta, gastos, extras), disclaimer «comparar ≠ reservar», empty, loading, error |
| Ficha | Cover, dirección, zona, precio, m², planta, gastos, extras, cuerpo, «ZAGUÁN no llama», volver al comparador |
| Login | Titular mesa, email, password, submit, error |
| Mesa | Lista: dirección, zona, renta, status, updatedAt, acciones, CTA Nueva, empty, error |
| Alta | Campos dirección, zona, tipo, renta, m², hab, planta, gastos, musts, cover, cuerpo, submit |
| Success | Mensaje, slug, «publicado ≠ visitado», CTA ver finca / volver a mesa |
5. Taxonomía
PublishStatus (Listing)
| Status API | Label UI | Semántica |
|---|---|---|
DRAFT | Borrador | El barrio no lo ve |
PUBLISHED | Publicado | Visible en compare y ficha; no implica visita |
Orden de mesa: updatedAt descendente (lo último tocado primero).
[DECISIÓN] No hay RESERVED / VISITED / OPTIONED: S3, mesa de 1 agente.
Zona
| API | Label UI | Seed |
|---|---|---|
USERA | Usera | amparo-14, yeserias-8, merced-2 |
VALLECAS | Vallecas | albufera-212 |
ENTREVIAS | Entrevías | sicilia-9 |
ORCASUR | Orcasur | alegria-3 |
Tipo
| API | Label UI | Seed |
|---|---|---|
ALQUILER | Alquiler | Las 6 fincas |
VENTA | Venta | Ninguna en seed → empty honesto |
[DECISIÓN] tipo no es ático/bajo: eso vive en floorLabel y extras.
Listing (líneas seed)
| slug | Zona | Dirección | m² | Hab | Renta | Status |
|---|---|---|---|---|---|---|
amparo-14 | Usera | Calle Amparo 14, 3º D | 68 | 2 | 890 € | PUBLISHED |
albufera-212 | Vallecas | Av. de la Albufera 212, 5º | 74 | 3 | 980 € | PUBLISHED |
yeserias-8 | Usera | Paseo de Yeserías 8, bajo | 62 | 2 | 820 € | PUBLISHED |
sicilia-9 | Entrevías | Calle Sicilia 9, 2º | 81 | 3 | 1050 € | PUBLISHED |
alegria-3 | Orcasur | Calle de la Alegría 3, ático | 55 | 1 | 760 € | PUBLISHED |
merced-2 | Usera | Calle de la Merced 2, 1º izq | 70 | 2 | 870 € | DRAFT |
[COMPROBADO] Cinco publicadas + un DRAFT cubren el must del brief. El DRAFT extra es para que la mesa no sea una lista de solo publicados. [DECISIÓN]
Musts (booleanos)
| API | Label UI | Semántica |
|---|---|---|
elevator | Ascensor | true = hay ascensor |
terrace | Terraza | true = terraza (patio de manzana se anota en extras, no es terraza) |
pets | Mascotas | true = se admiten |
renovation | Reforma | true = reformado / reforma hecha |
6. Modelo mental vs UI
| Modelo mental | Representación |
|---|---|
| “Busco en Usera” | Paso 1, chip Usera |
| “No paso de 950” | Paso 2, tope; copy criterio ≠ oferta |
| “Sin ascensor no subo” | Paso 3, toggle |
| “Enséñame tres” | /comparar 3 cols |
| “El de Amparo” | Columna → /fincas/amparo-14 |
| “¿Pido visita?” | No hay UI. Copy: comparar ≠ reservar. ZAGUÁN no llama |
| “Lo estoy fichando” | Badge Borrador en mesa |
| “Ya está en el escaparate” | Badge Publicado; entra en compare |
| “¿Ya ha venido alguien?” | Success / mesa: publicado ≠ visitado |
7. Query string y estado de criterio
El wizard persiste el criterio en el cliente (sessionStorage zaguan_criteria) y lo reenvía en POST /api/compare.
Únicos query admitidos en URL:
| Ruta | Query | Uso |
|---|---|---|
/login | ?next=/mesa | Redirect post-login |
/comparar | opcional eco de criterio | Deep-link Could; no Must |
[DECISIÓN] Sin ?q= ni chips de zona en una home-listado. Eso sería H-SEARCH.
Campos del criterio (cliente + POST compare):
zona, tipo, maxRent, rooms, elevator, terrace, pets, renovation
Paso 2 no avanza sin zona + tipo. Paso 3 no avanza sin maxRent + rooms. Comparar no corre sin los tres pasos. [DECISIÓN]
8. Rutas API alineadas a IA
| UI | API |
|---|---|
| Listado público / filtros | GET /api/listings (query opcional) |
| Comparador | POST /api/compare |
| Ficha | GET /api/listings/:slug |
| Login | POST /api/auth/login |
| Mesa list | GET /api/mesa/listings JWT |
| Alta | POST /api/listings JWT |
| Publicar / despublicar | PATCH /api/listings/:id { status } JWT |
[COMPROBADO] Contrato del encargo (Listing, AGENTE, compare). No se documentan visitas, leads ni POST /api/visits como Must.
9. Decisiones de IA descartadas
| Idea | Por qué no en L1 v1 |
|---|---|
/explorar + grid de fincas | Rompe H-WIZARD / F-CMP |
| Área “mis favoritos” | Cuenta de facto; contradice H3 |
/mesa/:id ficha CRM | L1 compacto; acciones en lista + form de alta |
| Search / mapa como home | H-SEARCH / H-MAP de otros días |
| Feed de “recién publicados” | H-FEED de RONDA |
| Tabla de fincas como home | H-TABLE de CESTA |
| Lookbook editorial | H-GALLERY de MARGEN |
/visita/:slug | Rompe H2 / microcopy Comparar ≠ reservar |
| Wizard de 4 pasos de publicación | F-ONB de CANTERA; el alta es 1 pantalla |
04-user-flows.md
04 — Flujos de usuario — ZAGUÁN
Convenciones
- Actor: Guest (BUSCADOR / PROPIETARIA) | AGENTE
- Éxito: resultado observable
- Errores: UI + código HTTP cuando aplica
- Flujo canónico: F-CMP (criterio 3 pasos → 3 columnas → ficha)
- Flujo de mesa: login → lista → alta → publicar
F1 — Wizard paso 1 · zona + tipo (BUSCADOR)
/ → H-WIZARD paso 1
→ chips zona · tipo ALQUILER | VENTA
→ sessionStorage.zaguan_criteria { zona, tipo }
→ Siguiente → /criterio/presupuesto
→ pie “Soy agente” → /login
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Aterriza; el paso 1 es el primer foco | Render home |
| 2 | Elige una zona (obligatorio) | Criterio cliente |
| 3 | Elige tipo (default ALQUILER) | Criterio cliente |
| 4 | Siguiente | Router + persist |
Éxito: criterio tiene zona + tipo; avanza al paso 2.
AC: no hay que hacer scroll de “marca” para llegar a los chips. No hay hero 2-col.
Errores / estados
| Caso | Comportamiento |
|---|---|
| Siguiente sin zona | Inline: “Elige una zona.” No navega |
| VENTA (sin seed) | Se permite elegir; el empty llega en F4 |
| Primera carga | Chrome S-WIZ + chips; no spinner de marca |
F2 — Wizard paso 2 · presupuesto + habitaciones (BUSCADOR)
/criterio/presupuesto
→ si no hay zona → redirect /
→ input/slider maxRent · chips 1 / 2 / 3+
→ disclaimer “Esto es un criterio, no una oferta.”
→ Atrás / Siguiente → /criterio/musts
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee el tope | Criterio.maxRent |
| 2 | Elige habitaciones | Criterio.rooms |
| 3 | Lee el disclaimer | UI |
Éxito: maxRent + rooms persistidos.
Rango demo: 500–1500 €, step 10. Default sugerido 950 (Marcos). [DECISIÓN]
| Caso | Comportamiento |
|---|---|
| maxRent vacío o < 500 | Inline; no avanza |
| rooms vacío | Inline; no avanza |
| Entrada directa a la URL | Redirect a / si falta zona |
F3 — Wizard paso 3 · musts (BUSCADOR)
/criterio/musts
→ si falta tope → redirect /criterio/presupuesto
→ toggles elevator, terrace, pets, renovation (default OFF)
→ Atrás / Comparar → POST /api/compare → /comparar
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Enciende solo lo que es must | Criterio booleanos |
| 2 | Comparar | POST compare |
Éxito: criterio completo; navega a 3 columnas o empty.
AC: OFF significa “me da igual”, no “sin ascensor”. [DECISIÓN]
Default OFF = no filtrar por ese must. ON = filtro duro.
F4 — Comparador 3 columnas (BUSCADOR)
/comparar → POST /api/compare { zona, tipo, maxRent, rooms, elevator, terrace, pets, renovation }
→ { criteria, listings[≤3] }
→ 3 cols | empty | error | skeleton
→ click columna → /fincas/:slug
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee el resumen de criterio (zona, tope, hab, musts) | criteria echo |
| 2 | Recorre precio, m², planta, gastos, extras | listings[3] |
| 3 | Abre ficha | Router |
| 4 | Lee “Comparar no es reservar.” | UI |
Éxito: hasta 3 fincas PUBLISHED alcanzables, o empty accionable.
AC columnas: cada una muestra precio, m², planta, gastos, extras. Cero botones Reservar / Te llamamos.
Ranking [DECISIÓN]
- Solo
PUBLISHED. - Filtros duros:
tipo,maxRent(renta ≤ tope), musts ON. - Preferencia: misma
zona, luegoroomsexactas, luego renta más cercana al tope por debajo. - Devuelve hasta 3. Si hay más de 3, top-3. Si hay 1–2, se muestran huecos vacíos (no se inventan fincas). Si hay 0, UI-10.
Guión Marcos (demo): zona USERA, ALQUILER, 950 €, 2 hab, musts OFF → amparo-14 (890) + yeserias-8 (820). Tercera columna vacía o, si se relajara zona, alegria-3 no entra por 1 hab. [COMPROBADO de seed] 2 matches exactos.
Errores / estados
| Caso | Comportamiento |
|---|---|
| 0 matches | UI-10: “Ninguna finca encaja con este criterio.” + Afloja un must / cambia zona |
| Red / 5xx | UI-11 + Reintentar (repite POST) |
| Primera carga | Skeleton de 3 columnas caliza (doc 16) |
| Sin criterio | Redirect / |
| VENTA | Empty honesto (seed vacío) |
[DECISIÓN] GET /api/listings con query es el listado crudo (Could en UI). El Must de producto es POST compare.
F5 — Ficha de finca (BUSCADOR)
/fincas/:slug → GET /api/listings/:slug
→ cover, dirección, zona, precio, m², planta, gastos, extras, cuerpo
→ “ZAGUÁN no llama. Si hay visita, la agencia llama.”
→ enlace Comparar → /comparar
→ 404 slug DRAFT o inexistente → mensaje + volver al wizard
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee dirección y precio | Listing |
| 2 | Lee planta, gastos, extras | Listing |
| 3 | Lee cuerpo | Listing.body |
| 4 | Decide otra finca o volver | Router |
Éxito: finca leíble; disclaimer visible; no hay reserva ni teléfono de captación.
Error: 404 si slug inexistente o status=DRAFT.
AC ficha
| # | Criterio |
|---|---|
| 1 | Foto real del cover (no color sólido) — salvo DRAFT sin foto |
| 2 | Dirección en Instrument Serif; zona en Sora hierro |
| 3 | Precio, m², planta, gastos, extras visibles sobre el fold |
| 4 | Disclaimer “ZAGUÁN no llama” visible sin scroll en desktop |
| 5 | Sin CTA de visita, lead o “te llamamos” |
[DECISIÓN] Un DRAFT nunca hace 200 en esta ruta. No se “previsualiza” en público ni con query ?preview=. Preview = L+1 con token.
F6 — Login JWT (AGENTE)
/login → POST /api/auth/login { email, password }
→ 200 { accessToken, user } → localStorage → /mesa (o ?next sanitizado)
→ 401 → mensaje error en form
| Campo | Validación cliente | API |
|---|---|---|
| required, email | @IsEmail() | |
| password | required, min 6 | @MinLength(6) |
Éxito: token guardado; user role=AGENT, nombre Lucía Ferrer.
Credencial demo: lucia@zaguan.barrio / password123.
| Caso | Comportamiento |
|---|---|
| Credenciales inválidas | 401 + mensaje |
| Red caída | Error de red en UI |
| Token caducado en mesa | 401 en GET → re-login |
F7 — Mesa de la agencia (AGENTE)
/mesa (token en cliente)
→ GET /api/mesa/listings
→ Render filas + badges DRAFT / PUBLISHED + CTA Nueva
| Caso | Comportamiento |
|---|---|
| Lista con items | Filas: dirección, zona, renta, status, updatedAt, acción Publicar si DRAFT |
| Lista vacía | Empty: “Aún no hay fincas. Da de alta la primera.” |
| Fallo red / 401 | Error + reintentar / re-login |
[DECISIÓN] Sin stats endpoint en v1. Recuentos DRAFT/PUBLISHED, si se muestran, se derivan del array.
Seed: 5 PUBLISHED + merced-2 DRAFT.
F8 — Alta de finca (AGENTE)
/mesa/nueva
→ validación cliente
→ POST /api/listings {
title, address, zona, tipo,
sqm, rooms, rent, expenses, floorLabel,
elevator, terrace, pets, renovation,
cover, body
}
→ 201 Listing { slug, status: DRAFT, ... }
→ o bien POST + PATCH { status: "PUBLISHED" } si elige Publicar
→ /mesa/ok/:slug
| Campo | Validación cliente (mín.) | API |
|---|---|---|
| title / address | required, min 4 | @MinLength(4) |
| zona | required, enum | USERA | VALLECAS | ENTREVIAS | ORCASUR |
| tipo | required, enum | ALQUILER | VENTA |
| sqm | required, > 0 | @Min(1) |
| rooms | required, 1–6 | @Min(1) @Max(6) |
| rent | required, > 0 | @Min(1) |
| expenses | required, ≥ 0 | @Min(0) |
| floorLabel | required, min 2 | @MinLength(2) |
| elevator, terrace, pets, renovation | boolean | default false |
| cover | required si Publicar; opcional en DRAFT | allowlist de assets |
| body | required, min 40 | @MinLength(40) |
| status | no en POST; siempre DRAFT al crear | servidor fuerza DRAFT |
Éxito: registro status=DRAFT (o PUBLISHED si eligió Publicar en el mismo viaje) + pantalla success.
No hay cuenta de buscador en v1.
Errores
| Caso | Comportamiento |
|---|---|
| Validación DTO | 400 + mensaje de campo |
| Red caída | Error de red en form; no navegar a /mesa/ok/… |
| Doble submit | Botón disabled mientras pending |
| body < 40 | 400 |
| Sin token | 401 |
[DECISIÓN] Crear siempre como DRAFT. El botón Publicar del form hace POST + PATCH. Ver doc 20.
[COMPROBADO] Entidad Listing según encargo.
F9 — Publicar desde la mesa (AGENTE)
/mesa
→ PATCH /api/listings/:id { status: "PUBLISHED" }
→ 200 Listing actualizado
→ opcional: /mesa/ok/:slug si se quiere el board de success
Transiciones (máquina simple L1)
| Desde | Hacia | Intención |
|---|---|---|
| DRAFT | PUBLISHED | Lucía cierra la finca; entra al comparador |
| PUBLISHED | DRAFT | Corrección / retirada temporal |
| — | RESERVED / VISITED | No en v1 |
Éxito: badge actualizado; la finca aparece en GET /api/listings y puede salir en compare.
Errores: 404 id; 401 sin token; 400 status inválido.
Regla de integridad (ops): Publicar no crea una visita, una llamada ni un lead. Publicado ≠ visitado. ZAGUÁN no llama.
F10 — Success de publicación (AGENTE)
/mesa/ok/:slug → copy de acuse + título/slug
→ “Publicado no es visitado.”
→ “El barrio ya puede compararla. Nadie ha subido todavía.”
→ CTA ver finca pública / volver a la mesa
| Paso | Acción | Sistema |
|---|---|---|
| 1 | Lee acuse | UI |
| 2 | Comprueba la finca en incógnito (Could) | Fuera / otra pestaña |
| 3 | Sigue con el siguiente DRAFT | /mesa |
AC: el disclaimer es visible sin scroll en desktop.
Si se entra a /mesa/ok/algo-que-no-existe: mensaje genérico + volver a mesa; no se inventa un registro. [DECISIÓN]
F11 — Logout (AGENTE)
Click "Salir" (mesa) → limpia localStorage (token + user) → /login
Matriz de errores global
| Código | Cuándo | UI |
|---|---|---|
| 400 | DTO inválido | Mensaje campo / genérico |
| 401 | Sin/mal token en mesa | Re-login |
| 404 | slug DRAFT, id o slug no existe | Mensaje + volver al wizard |
| 5xx / network | API caída | Error + retry |
Flujos fuera de alcance v1
- Registro / recuperación de password de AGENTE
- Cuenta de buscador / favoritos / alertas
- Reserva o confirmación de visita
- “Te llamamos” / captura de teléfono
- Notificación email “hay un piso nuevo”
- Filtros multi-criterio en mesa
- Detalle
/mesa/:id - Mapa / street view
- Pasarela / señal / contrato
05-data-model.md
05 — Modelo de datos — ZAGUÁN
1. Visión general
Dominio L1 de inmobiliaria de barrio + mesa de publicación:
| Entidad | Tabla SQL | Propósito |
|---|---|---|
| User | users | Identidad de agencia (rol AGENT) |
| Session | sessions | Sesión JWT / token persistido (Should) |
| Listing | listings | Finca en alquiler (o venta) |
| Comparison | comparisons | Criterio + hasta 3 slugs (persistencia opcional) |
Base: PostgreSQL (Neon project curly-recipe-23023272) · IDs: cuid() o entero serial.
[DECISIÓN] ensureSchema o Prisma son válidos si el contrato de tablas se cumple; el brief de stack no exige Prisma.
2. Enums (aplicación; persistidos como TEXT + check)
Role
| Valor | Descripción |
|---|---|
AGENT | Operadora de la mesa; único rol autenticado v1 |
PublishStatus
| Valor | Descripción |
|---|---|
DRAFT | Invisible en cara pública |
PUBLISHED | Visible en listings / compare / ficha |
Zona
USERA | VALLECAS | ENTREVIAS | ORCASUR
Tipo
ALQUILER | VENTA
3. Diagrama ER (texto)
users
id, email, password_hash, name, role(AGENT)
created_at, updated_at
1 ──< sessions
sessions
id, user_id → users
token_hash, expires_at
created_at
listings
id, slug (unique)
title, address, floor_label
zona (USERA | VALLECAS | ENTREVIAS | ORCASUR)
tipo (ALQUILER | VENTA)
sqm, rooms, rent, expenses
elevator, terrace, pets, renovation
cover, body
status (DRAFT | PUBLISHED)
created_at, updated_at
comparisons -- opcional
id
zona, tipo, max_rent, rooms
elevator, terrace, pets, renovation
listing_slugs (TEXT[] o JSON)
created_at
No hay FK entre
listingsyusersen v1: la agente opera sobre el conjunto global.
No hay FK buscador: comparar es anónimo.
4. Tablas
users
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| TEXT | UNIQUE NOT NULL | |
| password_hash | TEXT | NOT NULL (bcrypt) |
| name | TEXT | NOT NULL |
| role | TEXT | NOT NULL DEFAULT 'AGENT' |
| created_at | TIMESTAMPTZ | DEFAULT now() |
| updated_at | TIMESTAMPTZ | DEFAULT now() |
sessions
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| user_id | TEXT | NOT NULL FK → users(id) ON DELETE CASCADE |
| token_hash | TEXT | NOT NULL |
| expires_at | TIMESTAMPTZ | NOT NULL |
| created_at | TIMESTAMPTZ | DEFAULT now() |
[DECISIÓN] El JWT viaja en Authorization: Bearer. sessions permite invalidar (logout) sin esperar expiración. Si el slice usa JWT stateless, documentar el hueco; D-P1-05 se cumple igual con guard.
listings
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| slug | TEXT | UNIQUE NOT NULL, kebab-case |
| title | TEXT | NOT NULL |
| address | TEXT | NOT NULL |
| floor_label | TEXT | NOT NULL (3º D, bajo, ático) |
| zona | TEXT | NOT NULL |
| tipo | TEXT | NOT NULL DEFAULT 'ALQUILER' |
| sqm | INTEGER | NOT NULL CHECK > 0 |
| rooms | INTEGER | NOT NULL CHECK ≥ 1 |
| rent | INTEGER | NOT NULL CHECK > 0 · euros / mes |
| expenses | INTEGER | NOT NULL CHECK ≥ 0 · comunidad € / mes |
| elevator | BOOLEAN | NOT NULL DEFAULT false |
| terrace | BOOLEAN | NOT NULL DEFAULT false |
| pets | BOOLEAN | NOT NULL DEFAULT false |
| renovation | BOOLEAN | NOT NULL DEFAULT false |
| cover | TEXT | filename (amparo.jpg) · NULL en DRAFT sin foto |
| body | TEXT | NOT NULL |
| status | TEXT | NOT NULL DEFAULT 'DRAFT' |
| created_at / updated_at | TIMESTAMPTZ |
extras no se persiste como columna suelta: se deriva para el comparador (patio vive en body / un campo libre opcional extras TEXT). [DECISIÓN v1] columna opcional extras TEXT para “patio”, “terraza”, “mascotas OK”, “reforma”. Seed la rellena.
| extras seed | Origen |
|---|---|
| patio | amparo-14 |
| terraza | albufera-212, alegria-3 |
| mascotas OK | yeserias-8 |
| reforma | sicilia-9 |
| patio de luces | merced-2 |
comparisons (opcional)
| Campo | Tipo | Constraints |
|---|---|---|
| id | TEXT | PK |
| zona | TEXT | NOT NULL |
| tipo | TEXT | NOT NULL |
| max_rent | INTEGER | NOT NULL |
| rooms | INTEGER | NOT NULL |
| elevator / terrace / pets / renovation | BOOLEAN | NULL = OFF (no filtrar) |
| listing_slugs | TEXT[] / JSON | hasta 3 |
| created_at | TIMESTAMPTZ |
[DECISIÓN] Persistencia opcional. El Must del contrato es el response { criteria, listings }. Si no hay tabla, el POST compara en caliente y no guarda.
5. Reglas de integridad y negocio
| Regla | Implementación |
|---|---|
| Lectura pública de fincas | GET /api/listings sin guard; solo PUBLISHED |
| Lectura pública de ficha | GET /api/listings/:slug 200 solo si PUBLISHED; DRAFT → 404 |
| Compare | POST /api/compare público; solo PUBLISHED; hasta 3 |
| List / create / patch listings | JWT AGENT → 401 sin token |
| Slug único | kebab del título o dirección + sufijo -2 si colisión [DECISIÓN] |
| Status default listing | DRAFT al crear |
| Password | Nunca en claro; solo password_hash |
| Orden listings GET | rent asc (se compara precio, no “destacados”) |
| Orden mesa | updated_at desc |
| Orden compare | score (zona → rooms → cercanía al tope) |
| Publicar Listing | no crea visita ni llamada |
| Público nunca lista DRAFT | GET listings filtra; no hay ?includeDrafts |
| maxRent | rent <= maxRent |
| Must ON | igualdad true; OFF / omitido = no filtra |
6. Contratos API (resumen)
GET /api/listings (público)
Query opcional: zona, maxRent, rooms, elevator, terrace, pets, renovation.
Response 200: ListingCard[] solo PUBLISHED.
[
{
"id": "lst_amparo",
"slug": "amparo-14",
"title": "Calle Amparo 14, 3º D",
"address": "Calle Amparo 14, 3º D",
"floorLabel": "3º D",
"zona": "USERA",
"tipo": "ALQUILER",
"sqm": 68,
"rooms": 2,
"rent": 890,
"expenses": 42,
"elevator": false,
"terrace": false,
"pets": false,
"renovation": false,
"extras": "patio",
"cover": "amparo.jpg",
"status": "PUBLISHED"
}
]
JSON camelCase hacia el web. [DECISIÓN]
POST /api/compare (público)
Body
| Campo | Tipo | Req |
|---|---|---|
| zona | Zona | sí |
| tipo | Tipo | sí |
| maxRent | number | sí |
| rooms | number | sí |
| elevator | boolean | no |
| terrace | boolean | no |
| pets | boolean | no |
| renovation | boolean | no |
Response 200
{
"criteria": {
"zona": "USERA",
"tipo": "ALQUILER",
"maxRent": 950,
"rooms": 2,
"elevator": false,
"terrace": false,
"pets": false,
"renovation": false
},
"listings": [ "/* hasta 3 ListingCard */" ]
}
Si 0 matches: { criteria, listings: [] } + empty UI-10.
Si se persiste Comparison: el id no es Must en response.
GET /api/listings/:slug (público)
Listing completa (body) o 404 (inexistente o DRAFT).
POST /api/auth/login
Body: { email, password }
Response 200: { accessToken, user: { id, email, name, role } }
GET /api/mesa/listings (JWT)
Array Listing (incluir status), updatedAt desc. Sin token → 401. Incluye DRAFT.
POST /api/listings (JWT)
Body
| Campo | Tipo | Req |
|---|---|---|
| title | string | sí |
| address | string | sí |
| floorLabel | string | sí |
| zona | Zona | sí |
| tipo | Tipo | no (default ALQUILER) |
| sqm | number | sí |
| rooms | number | sí |
| rent | number | sí |
| expenses | number | sí |
| elevator / terrace / pets / renovation | boolean | no |
| extras | string | no |
| cover | string | no |
| body | string | sí |
Response 201: Listing + slug + status: "DRAFT".
PATCH /api/listings/:id (JWT)
Body: { status?: PublishStatus, ...campos editables }
Mínimo Must: { status }.
Response: Listing actualizada. Sin token → 401.
[COMPROBADO] PATCH sobre :id con { status }.
7. Seed de referencia (2026-08-18)
AGENTE
| Nombre | Password | |
|---|---|---|
| Lucía Ferrer | lucia@zaguan.barrio | password123 |
[COMPROBADO] Credencial del encargo. Nombre de pila es constructo de diseño.
Listings PUBLISHED
| slug | Dirección | Zona | m² | Hab | Renta | Planta | Musts / extras | Cover |
|---|---|---|---|---|---|---|---|---|
| amparo-14 | Calle Amparo 14, 3º D | Usera | 68 | 2 | 890 € | 3ª SIN ascensor | patio | amparo.jpg |
| albufera-212 | Av. de la Albufera 212, 5º | Vallecas | 74 | 3 | 980 € | 5ª CON ascensor | terraza | albufera.jpg |
| yeserias-8 | Paseo de Yeserías 8, bajo | Usera | 62 | 2 | 820 € | bajo | mascotas OK | yeserias.jpg |
| sicilia-9 | Calle Sicilia 9, 2º | Entrevías | 81 | 3 | 1050 € | 2ª CON ascensor | reforma | sicilia.jpg |
| alegria-3 | Calle de la Alegría 3, ático | Orcasur | 55 | 1 | 760 € | ático SIN ascensor | terraza | alegria.jpg |
Listing DRAFT
| slug | Dirección | Zona | m² | Hab | Renta | Notas |
|---|---|---|---|---|---|---|
| merced-2 | Calle de la Merced 2, 1º izq | Usera | 70 | 2 | 870 € | Sin foto pública · patio de luces · reforma parcial · no sale en GET público |
Gastos (expenses) son catálogo demo, no cuotas reales de comunidad. [SUPUESTO de catálogo]
| slug | expenses €/mes |
|---|---|
| amparo-14 | 42 |
| albufera-212 | 68 |
| yeserias-8 | 35 |
| sicilia-9 | 55 |
| alegria-3 | 30 |
| merced-2 | 40 |
Textos de body son contenido de producto demo, no anuncios de una agencia real. [SUPUESTO de catálogo]
| slug | Body demo (extracto) |
|---|---|
| amparo-14 | Tercero a la calle, dos habitaciones al patio de manzana. Sin ascensor. La cocina toma la luz. Usera, a un salto de Marcelo Usera. |
| albufera-212 | Quinto con ascensor y terraza a la avenida. Tres habitaciones. Vallecas, cerca del puente. |
| yeserias-8 | Bajo con salida a un patio. Se admiten mascotas. Dos habitaciones. Yeserías, al borde de Usera. |
| sicilia-9 | Segundo reformado, ascensor, tres habitaciones. Entrevías. El más caro del seed. |
| alegria-3 | Ático de un dormitorio con terraza. Sin ascensor. Orcasur. |
| merced-2 | Borrador: primero interior, patio de luces. Lucía aún no ha subido fotos. Invisible en el comparador. |
Las seis fincas son seed de producto. No se presentan como cartera real de una agencia de Marcelo Usera. [SUPUESTO de catálogo]
8. Evolución posible (no implementada)
| Cambio | Nivel |
|---|---|
| Persistencia obligatoria de Comparison + replay | L+1 |
agentUserId + segundo agente | L+1 / L3 |
| Preview token de DRAFT | L+1 |
| Soft delete + audit log de status | L3 |
| Visitas / calendario | Fuera (H2) |
| Lead / teléfono de buscador | Fuera (S2) |
| Mapa / geocode | Fuera (terna) |
06-tech-stack.md
06 — Stack tecnológico — ZAGUÁN
1. Visión
| Capa | Tecnología | Notas |
|---|---|---|
| Frontend | Angular standalone + signals | Puerto 4200 |
| Estilos | Tailwind CSS | Tokens rusted (hormigón/grafito/óxido/hierro/caliza/junta) |
| Backend | NestJS TypeScript | Puerto 3018, prefijo /api |
| Persistencia | PostgreSQL (ensureSchema o Prisma) | Tablas users, sessions?, listings, comparisons? |
| DB | Neon PostgreSQL | project curly-recipe-23023272 |
| Auth | JWT | Rol AGENT |
| Diseño | Paper | 01M09V13JEANPFFD6DS1RCN29G |
| Repo | /Users/cristian/orca/zaguan-app/ | GitHub Criscode2022/zaguan-app |
Stack fijo del cron (D-P0-03). No React/Next/Firebase.
[COMPROBADO] Puerto 3018, Neon, Paper y repo constan en el encargo.
2. Estructura
zaguan-app/
├── apps/api/ # Nest (paquete independiente)
├── apps/web/ # Angular + Tailwind (paquete independiente)
└── package.json # scripts api / start · apps independientes
D-P1-03: npm --prefix o pnpm --filter. Prohibido npm workspaces que rompen Angular (AP-06).
Scripts raíz esperados:
| Script | Acción |
|---|---|
| api | Arranca Nest en :3018 |
| start / web | Arranca Angular en :4200 |
3. API pública vs JWT
| Método | Ruta | Auth |
|---|---|---|
| GET | /api/listings | Público (solo PUBLISHED; query opcional) |
| POST | /api/compare | Público |
| GET | /api/listings/:slug | Público (404 si DRAFT) |
| POST | /api/auth/login | Público |
| GET | /api/mesa/listings | JWT AGENT |
| POST | /api/listings | JWT AGENT |
| PATCH | /api/listings/:id | JWT AGENT |
CORS: origen http://localhost:4200 en local.
[COMPROBADO] Superficie API del encargo. No se añaden Must extra (/visits, /leads, /favorites).
4. Justificación
| Elección | Razón |
|---|---|
| Angular + Nest | Alineación con la serie daily y el handoff del estudio |
| Neon serverless | Volumen bajo (S1: 8–20 fincas; demo 5+1); no cola Redis |
| JWT AGENT | D-P1-05: panel interno cerrado desde el día 1 |
| Filtro PUBLISHED en servidor | Source of truth = SQL; el comparador no “esconde” DRAFT en cliente |
| Puerto 3018 | Evitar colisión con MARGEN :3017 / CESTA :3016 / RONDA :3015 |
5. Variables
.env local (nunca git):
| Variable | Servicio | Descripción |
|---|---|---|
DATABASE_URL | API | Neon curly-recipe-23023272 |
JWT_SECRET | API | Firma tokens |
PORT | API | opcional, 3018 |
JWT_EXPIRES | API | opcional (p. ej. 12h) |
.env.example solo placeholders.
Web: URL de API en ApiService (default http://localhost:3018/api).
6. Dependencias de producto (mínimas)
API
@nestjs/common/core/platform-express@nestjs/jwt(o verificación manual)bcrypt(obcryptjs)- cliente Postgres (
pg) o Prisma class-validator+class-transformer
Web
- Angular standalone
- Tailwind 3
- fuentes: Instrument Serif + Sora (Google Fonts o self-host)
7. Arranque (contrato)
cd /Users/cristian/orca/zaguan-app
# instalar apps independientes (npm --prefix o pnpm --filter)
# apps/api/.env → DATABASE_URL + JWT_SECRET + PORT=3018
# migrate / ensureSchema + seed
# api → http://localhost:3018
# web → http://localhost:4200
8. Lo que este stack no es
| No | Por qué |
|---|---|
| npm workspaces | AP-06 |
| Firebase Auth | Stack fijo JWT + Neon |
| Next.js / React | D-P0-03 |
| Pasarela de pago / señal | S4 |
| Elasticsearch | 5 fincas no lo necesitan |
| CRM headless | S3: mesa de 1 agente, no tipos |
9. Nota sobre el repo existente
[COMPROBADO] Existe /Users/cristian/orca/zaguan-app y el remoto previsto Criscode2022/zaguan-app.
El scaffold contiene leftover de MARGEN (issues, pieces, events, fotos de semanario en public/assets/). Esta suite define el contrato ZAGUÁ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 — ZAGUÁN
1. Concepto
ZAGUÁN es un umbral de finca en pantalla: se entra con un criterio y se salen tres pisos, no una landing de “inmobiliaria digital”. El ground es hormigón fresco. Las columnas se apoyan como portales en una acera. El óxido es la reja, no un botón de marketing.
Mood elegido: rusted.
Primer instinto descartado: mineral / agencia caliza (piedra clara, serif de centro, “lujo sereno”). Ese mood ya huele a estudio de Chamberí. ZAGUÁN es Marcelo Usera, reja y junta.
[COMPROBADO] Mood, ground y paleta salen de docs/00-day-brief.md.
2. Territorio visual
| Eje | Sí | No |
|---|---|---|
| Superficie | Hormigón #E3E1DC, junta, card caliza | Papel MARGEN, musgo CESTA, cromo RONDA, agua DERIVA, chalky PIZARRA |
| Tipo | Instrument Serif display + Sora UI | Cormorant, Public Sans, Newsreader, Outfit, Spectral, Manrope, Zilla, Mulish, Lora, Karla |
| Foto | Finca de barrio, mediodía, reja, tendedero | Stock de loft blanco, skyline, “pareja feliz con llaves” |
| Grid | Wizard centrado + comparador 3 cols | Hero 2-col + 3 cards de features (AP-12) |
| Acento | Óxido #B54A2A en paso activo y PUBLISHED | Cadmio editorial, naranja ONG, teal seguro |
| Chrome | Pasos 1—2—3 | Masthead de periódico, rail, dock, sidebar CRM |
3. Paleta
| Token | Hex | Rol |
|---|---|---|
| Hormigón / G-CONCRETE | #E3E1DC | Ground de página. Gris mineral frío, no beige. |
| Grafito | #1F1D1B | Texto, wordmark, cuerpo. Casi negro, no púrpura. |
| Óxido | #B54A2A | Paso activo, acento, badge PUBLISHED, CTA |
| Hierro | #6A6560 | Meta, labels, nav idle, planta, gastos |
| Caliza | #F3F1EC | Solo cards (columna, ficha, fila). Nunca el fondo de página. |
| Junta | #C9C4BB | Rules, bordes, skeletons |
Jerarquía de color: el hormigón carga; el óxido puntúa; la caliza encuadra la foto. Si el óxido pinta fondos enteros, deja de ser reja y se vuelve “brand rust”. [DECISIÓN]
4. Tipografía
| Uso | Familia | Tamaños de referencia |
|---|---|---|
| Wordmark | Instrument Serif 600 | 28–32 desktop · 24 mobile |
| Titular de paso | Instrument Serif 600 | 40–56 |
| Dirección de finca | Instrument Serif 600 | 28–36 desktop · 24 mobile |
| Precio | Sora 600 | 24–28 |
| Cuerpo de ficha | Sora 400 | 16–17 / 26–28 lh |
| UI, pasos, form, comparador | Sora 400/500/600 | 14 / 16 |
| Meta (planta, gastos, zona) | Sora 500 | 12–13, tracking 0.04em, hierro |
Prohibido: Instrument Serif por debajo de 20px (se rompe). Sora en el wordmark (se vuelve startup).
Fuentes vetadas de la serie: Cormorant / Public Sans / Spectral / Manrope / Zilla / Mulish / Newsreader / Outfit / Lora / Karla.
5. Fotografía
Las imágenes del case son el sistema, no un moodboard:
| Asset | Lectura visual | Uso |
|---|---|---|
hero.jpg | Zaguán / portal, reja, hormigón | Cover UX · atmósfera, no hero de marketing |
listings/amparo.jpg | Finca de Usera, patio, tercero | amparo-14 |
listings/albufera.jpg | Bloque a la avenida, terraza, quinto | albufera-212 |
listings/yeserias.jpg | Bajo a la calle, umbral | yeserias-8 |
listings/sicilia.jpg | Portal reformado, Entrevías | sicilia-9 |
listings/alegria.jpg | Ático, terraza, Orcasur | alegria-3 |
Crop: columnas y ficha en 4:3 o 3:2 (fachada). No recorte 1:1 que corta el portal. [DECISIÓN] 4:3 de finca, distinto del 16:10 editorial de MARGEN.
Tratamiento: saturación real, sin LUT “real estate teal”, sin cielo HDR. El óxido no se superpone como filtro: vive en UI.
El DRAFT merced-2 no tiene foto pública. En mesa: placeholder junta + texto “sin foto”.
6. Layout
Chrome de pasos (S-WIZ)
ZAGUÁN 1 Zona 2 Presupuesto 3 Musts
──────────────────────────────────────────── (rule junta 1px; óxido bajo el activo)
Wordmark a la izquierda. Pasos a la derecha o centrados. Sin hamburger en desktop. Sin “Agenda / Número / Blog”.
Home (H-WIZARD)
- Titular Instrument Serif: ¿Por dónde entras?
- Chips de zona (4) + tipo (2).
- CTA Siguiente. Pie “Soy agente”.
No hay portada fotográfica a 60vh. hero.jpg puede vivir en UX-00 Cover, no como hero de producto. [DECISIÓN]
Comparador (F-CMP)
3 columnas caliza, misma retícula: foto 4:3 → dirección → precio → fila m² / planta / gastos → extras. Disclaimer bajo el criterio, sobre las columnas.
Ficha
Cover a ancho de columna (~720px), no full-bleed de marketing. Dirección → precio → datos → cuerpo. Blancos de umbral, no de landing.
Mesa
Lista densa sobre hormigón. No imita el comparador. Es trastienda: filas, badges, CTA óxido. Lucía no necesita “inspiración de marca” para publicar.
7. Motion
| Superficie | Spec |
|---|---|
| Hover columna | Junta grafito; no elevación 8px ni scale 1.04 |
| Cambio de paso | Fade 160ms o slide 8px; off si reduced-motion |
| Skeleton | Pulso 1.2s en junta; off si reduced-motion |
| Publicar | Badge, no confetti ni “llaves animadas” |
Prohibido: parallax en portal, scroll-jacking, page transitions de 400ms, sticker de “NUEVO” parpadeante.
8. Anti-clichés (serie ALS-2)
| Cliché | Por qué no |
|---|---|
| Agencia caliza mineral | Primer instinto del brief; vetado |
| Papel + cadmio kiosco (MARGEN) | Editorial, no finca |
| Botanical / musgo (CESTA) | Despensa |
| Chrome / neón (RONDA) | Gaming |
| Chalky municipal (PIZARRA) | Inscripción |
| Pareja stock con llaves gigantes | Mentira social del “ya es tuyo” |
| Lead blur + “te llamamos” | Rompe S2 / H2 |
| Iconos outline de casita 2014 | Chrome tipográfico basta |
9. Tono de marca (visual + verbal, puente a doc 09)
Cercano de portal y de reja. Tuteo. Segunda persona cuando habla la UI. Tercera cuando habla la ficha.
El producto se compara y se publica. No vende una hipoteca, no pide visita, no pide el móvil.
10. Criterios de aceptación creativos
- Ground de página =
#E3E1DC. Cards =#F3F1EC. Nunca al revés. - Óxido en el paso activo y en el CTA, no un header naranja lleno.
- Comparador de 3 columnas visible en el primer viewport desktop (tras el criterio).
- Ninguna fuente de la lista vetada.
- Home reconocible como paso 1, no como hero 2-col de startup.
- Success de mesa no celebra una “visita confirmada”.
08-design-system.md
08 — Design system — ZAGUÁN
1. Tokens
--color-concrete: #E3E1DC; /* G-CONCRETE ground */
--color-graphite: #1F1D1B;
--color-rust: #B54A2A;
--color-iron: #6A6560;
--color-limewash: #F3F1EC; /* cards only — caliza */
--color-joint: #C9C4BB;
--color-draft: #6A6560; /* badge DRAFT */
--color-live: #B54A2A; /* badge PUBLISHED */
--font-display: "Instrument Serif", "Iowan Old Style", serif;
--font-ui: "Sora", system-ui, sans-serif;
--radius-card: 2px; /* junta de finca, no pill */
--radius-cta: 2px;
--rule-wiz: 1px solid var(--color-joint);
--space: 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64;
Tailwind (referencia):
| Key | Valor |
|---|---|
colors.concrete | #E3E1DC |
colors.graphite | #1F1D1B |
colors.rust | #B54A2A |
colors.iron | #6A6560 |
colors.limewash | #F3F1EC |
colors.joint | #C9C4BB |
fontFamily.display | Instrument Serif |
fontFamily.sans | Sora |
[COMPROBADO] Hex y tipo = brief.
2. Escala tipográfica
| Token | Familia | Size / lh | Uso |
|---|---|---|---|
display-xl | Instrument 600 | 56 / 60 | Titular paso 1 desktop |
display-l | Instrument 600 | 40 / 44 | Titular paso mobile · dirección ficha desktop |
display-m | Instrument 600 | 28 / 32 | Dirección columna · wordmark |
price | Sora 600 | 24 / 28 | Renta €/mes |
body | Sora 400 | 16 / 26 | Cuerpo de ficha |
ui | Sora 400 | 16 / 24 | Form, comparador, lista mesa |
meta | Sora 500 | 13 / 18 | Zona, planta, gastos |
step | Sora 600 | 12 / 16 · tracking 0.06em · uppercase | 1 Zona · 2 Presupuesto · 3 Musts |
3. Componentes
Chrome de pasos (S-WIZ)
| Spec | Valor |
|---|---|
| Alto desktop | 64–72px |
| Alto 390 | 56px + “1 de 3” |
| Fondo | Hormigón |
| Rule | 1px junta full-bleed |
| Paso activo | Sora 12 · óxido + filete 2px óxido |
| Paso idle | Hierro |
| Paso bloqueado | Hierro 50% · no es enlace |
Slots: 1 Zona / · 2 Presupuesto /criterio/presupuesto · 3 Musts /criterio/musts.
Wordmark ZAGUÁN → / (reinicia).
Mesa no vive aquí.
Chip de zona / tipo / hab
┌──────────┐
│ Usera │ idle: caliza + junta
└──────────┘ selected: óxido texto o rule 2px óxido
<button type="button">conaria-pressed.- Target ≥44px.
- No pills 999px: radius 2.
Columna de comparador
┌─────────────────────────────┐
│ cover 4:3 │
├─────────────────────────────┤
│ Dirección Instrument │
│ 890 €/mes │
│ 68 m² · 3º D · 42 € gast. │
│ patio │
└─────────────────────────────┘
- Fondo caliza, sin sombra.
- Toda la columna es un
<a>a/fincas/:slug. - Hover: rule 1px grafito. Sin lift.
- Hueco vacío (menos de 3): caliza + junta dashed + “Sin tercera finca”.
Ficha de finca
| Bloque | Spec |
|---|---|
| Columna | max-width 720px, centrada |
| Cover | 4:3, full de columna |
| Dirección | display-l / display-m |
| Precio | price + “/ mes” meta |
| Datos | grid 2×2: m², planta, gastos, extras |
| Cuerpo | body 16/26 · max 70ch |
| Disclaimer | “ZAGUÁN no llama. Si hay visita, la agencia llama.” |
| Volver | link “← Las tres fincas” |
Fila de mesa
| Columna | Contenido |
|---|---|
| Dirección | 1 línea, truncate |
| Zona | USERA / … |
| Renta | 890 € |
| Status | Badge Borrador / Publicado |
| updatedAt | meta, Europe/Madrid |
| Acción | Publicar (si DRAFT) · ver |
Badge DRAFT: texto hierro, rule junta.
Badge PUBLISHED: texto óxido o caliza sobre óxido ≥14 bold.
Botones
| Variante | Uso | Spec |
|---|---|---|
| Primary | Siguiente, Comparar, Publicar, Entrar | Fondo óxido, texto caliza, h-44, radius 2 |
| Secondary | Atrás, Guardar borrador | Rule grafito, texto grafito, fondo hormigón |
| Ghost | Soy agente, Reintentar, Salir | Texto hierro |
| Danger | — | No hay borrado Must |
Pending: disabled + aria-busy. No spinner de marca.
Form
Labels visibles (no placeholder-only). Inputs h-44, radius 2, rule junta, foco outline 2px óxido offset 2. Textarea cuerpo min 6 filas. Select de zona, tipo, cover (allowlist).
Tope de renta: input numérico + (Should) slider. El valor se lee en Sora 24.
Toggles de musts: switch o checkbox grande, label a la derecha. OFF = “me da igual”.
Estados
| Estado | Receta |
|---|---|
| Empty compare | Titular Instrument 32 + 1 línea Sora + CTA “Afloja un must” → paso 3 |
| Empty mesa | Titular + CTA Nueva finca |
| Error | Banner junta óxido + texto + Reintentar |
| Loading | Skeleton 3 columnas caliza; wizard: no skeleton (es form) |
| 404 finca | “Esa finca no está publicada.” + link al wizard |
4. Grid y breakpoints
| Viewport | Comportamiento |
|---|---|
| ≥1200 | Wizard max 720; comparador 3 col iguales; mesa tabla |
| 960–1199 | Comparador 3 col compactas; mesa tabla |
| 600–959 | Comparador scroll-x snap; mesa lista |
| ~390 (mobile wizard) | Un paso por pantalla; chips wrap; comparador scroll-x; mesa apilada |
Padding horizontal: 24 desktop, 16 mobile.
Main en 390: padding-bottom 24 (no hay rail inferior).
5. Iconografía
Mínima. El zaguán no es un set de iconos outline de casita.
| Sitio | Recurso |
|---|---|
| Chrome | Texto. Sin icono de “home”. |
| Musts | Nada o un trazo 1.5 de 16px (reja / terraza) |
| Empty | Nada o una rule óxido de 32px |
| Status | Texto de badge, no semáforo |
Si hace falta un icono (salir, error): trazo 1.5, 20px, grafito. No pack Remix/Lucide a color.
6. Elevación y borde
| Superficie | Tratamiento |
|---|---|
| Página | Hormigón plano |
| Card / columna | Caliza plana + rule 1px junta |
| Mesa | Filas separadas por rule junta |
| Modal | No hay modal Must |
Cero sombras shadow-lg. El portal no flota.
7. Mapa componente → ruta
| Componente | Rutas |
|---|---|
WizardChromeComponent | /, /criterio/presupuesto, /criterio/musts |
ZonaChips | / |
RentField / RoomChips | /criterio/presupuesto |
MustToggles | /criterio/musts |
CompareColumn | /comparar |
FincaArticle | /fincas/:slug |
DeskRow | /mesa |
ListingForm | /mesa/nueva |
EmptyState / ErrorBanner / SkeletonCompare | /comparar, /mesa |
8. Criterios de aceptación DS
- Tokens Tailwind = hex del brief, sin “casi blanco”
#FAFAFAcomo ground. - Cards en caliza; página en hormigón.
- Primary CTA nunca en hierro sobre junta (contraste).
- Instrument Serif no aparece en labels de form ni en badges.
- Recorte de finca
aspect-[4/3], no 16:10 editorial ni 1:1. - Targets de chips, pasos y CTA ≥44px.
09-content-guide.md
09 — Guía de contenido — ZAGUÁN
1. Voz
Cercana de portal y de reja. Tuteo en UI. Segunda persona cuando la interfaz habla. Tercera cuando habla la ficha.
Sin jerga de portales (“inmueble premium”, “oportunidad única”, “no te lo pierdas”). Sin sede electrónica. Sin “te llamamos en 5 minutos”.
El producto se compara y se publica. No vende una hipoteca, no pide visita, no pide el móvil.
2. Palabras permitidas / prohibidas
| Usar | No usar |
|---|---|
| Finca / piso | Inmueble premium, propiedad, “home” |
| Criterio / tope | Oferta, puja, “tu presupuesto vinculante” |
| Comparar / columnas | Reservar, pedir visita, “apuntarme” |
| Publicar / publicado | Visitado, “ya ha venido gente”, “en opción” |
| Borrador | Pending review, staging, “oculto al algoritmo” |
| Mesa / agencia | Inbox, CRM, backoffice, captación |
| Buscador / pareja | Usuario, lead, prospecto |
| Agente | Community manager, closer, hunter |
| ZAGUÁN no llama | Te llamamos, reserva tu slot |
3. Microcopy duro (literales irrenunciables)
El brief fija cuatro oposiciones. Aparecen donde el usuario puede confundirse.
| Oposición | Dónde | Copy |
|---|---|---|
| Comparar ≠ reservar | Comparador sobre las columnas · ficha | “Comparar no es reservar. ZAGUÁN no pide ni confirma una visita.” |
| Criterio ≠ oferta | Paso 2 sobre el tope · resumen del comparador | “Esto es un criterio, no una oferta.” |
| Publicado ≠ visitado | Success de mesa · badge de mesa | “Publicado no es visitado. El barrio ya puede compararla; nadie ha subido todavía.” |
| ZAGUÁN no llama | Ficha · pie del comparador | “ZAGUÁN no llama. Si hay visita, la agencia llama.” |
[DECISIÓN] Los cuatro textos son obligatorios en UI. No se diluyen a un tooltip.
4. Microcopy por superficie
| Superficie | Copy |
|---|---|
| Eslogan | Tres fincas. Un criterio. |
| Línea de distrito | Usera · Vallecas · Entrevías · Orcasur |
| Paso 1 titular | ¿Por dónde entras? |
| Paso 1 ayuda | Elige zona y tipo. Luego el tope. |
| Paso 2 titular | ¿Hasta dónde llegas al mes? |
| Paso 2 disclaimer | Esto es un criterio, no una oferta. |
| Paso 3 titular | ¿Qué no negocias? |
| Paso 3 ayuda | Apagado = te da igual. Encendido = tiene que estar. |
| Chip CTA | Siguiente · Comparar estas tres |
| Atrás | Atrás |
| Card CTA implícito | Toda la columna es el enlace; no “Ver más” |
| Ficha volver | ← Las tres fincas |
| Comparador titular | Tres fincas con tu criterio |
| Comparador disclaimer | Comparar no es reservar. ZAGUÁN no pide ni confirma una visita. |
| Empty comparador | Ninguna finca encaja con este criterio. Afloja un must o sube el tope. |
| Empty mesa | Aún no hay fincas. Da de alta la primera. |
| Error | No hemos podido cargar las fincas. |
| Error retry | Reintentar |
| Loading (doc 16) | Cruzando el zaguán… |
| Login titular | Mesa |
| Login submit | Entrar |
| Login error | Email o contraseña no valen. |
| Mesa titular | Mesa |
| Mesa CTA | Nueva finca |
| PATCH PUBLISHED | Publicar |
| PATCH DRAFT | Volver a borrador |
| Alta título | Nueva finca |
| Alta submit secundario | Guardar borrador |
| Alta submit primario | Publicar |
| Success titular | Publicada. |
| Success cuerpo | El barrio ya puede compararla. Publicado no es visitado. Nadie ha subido todavía. |
| 404 DRAFT | Esa finca no está publicada. |
| Pie wizard | Soy agente |
| Salir | Salir |
| Hueco 3.ª col | Sin tercera finca con este criterio. |
5. Fichas seed (tono)
Las fincas demo se escriben como nota de portal de barrio, no como folleto de obra nueva ni como copy de lujo.
| Finca | Tono |
|---|---|
| Amparo 14 | Observación: tercero, patio, sin ascensor, luz de cocina. Sin “luminoso y acogedor”. |
| Albufera 212 | Quinto, terraza a la avenida, ascensor. Sin “espectacular”. |
| Yeserías 8 | Bajo, mascotas, patio. Sin atacar a una comunidad real. |
| Sicilia 9 | Reformado, Entrevías, tres hab. El precio se dice; no se justifica. |
| Alegría 3 | Ático, una hab, terraza, sin ascensor. Orcasur. |
| DRAFT Merced 2 | Oficio: sin foto aún. Invisible en público. |
No inventar declaraciones de vecinos reales. Si hay una voz, es de la ficha (neutra) o de persona de diseño etiquetada.
6. Nombres propios del seed
| Nombre | Uso |
|---|---|
| Marcos Vidal | BUSCADOR; no deja PII en producto |
| Lucía Ferrer | AGENTE; lucia@zaguan.barrio |
| Inés Roldán | PROPIETARIA del 3º de Amparo; no es cuenta |
| Marcelo Usera | Local de atrezzo de Lucía; no se afirma como negocio real |
No inventar CIF, número de colegiada, ni precios oficiales de comunidad. [SUPUESTO de catálogo demo]
Rentas seed: 890 · 980 · 820 · 1050 · 760 · 870. No se afirman como mercado 2026.
7. Tono por estado (mesa)
| Status | Frase mesa | Frase que Lucía podría decir (fuera de app) |
|---|---|---|
| DRAFT | El barrio no lo ve | “Aún no. Está en la mesa.” |
| PUBLISHED | En el comparador | “Ya está. No, nadie ha subido todavía.” |
La app no envía esos mensajes en v1. El copy solo prepara el habla.
8. Capitalización y números
- Wordmark: ZAGUÁN en versales Instrument Serif (con tilde).
- Zonas: Usera, Vallecas, Entrevías, Orcasur (con mayúscula).
- Renta:
890 €/mes(espacio ante €, 24h no aplica). - Superficie:
68 m²(espacio, superíndice). - Planta:
3º D·bajo·ático(minúscula en común). - Gastos:
42 € comunidad. - No mezclar “€890/mo” estilo US.
9. Idioma
es-ES. Fácil lectura. EN no en v1.
No mezclar “listing” en UI. En API sí: listings, maxRent.
10. Accesibilidad de copy
- No transmitir estado solo con color (“el óxido significa publicado”).
- Badge lleva texto: Borrador, Publicado.
- Alt de fotos: descriptivo de la finca, no “imagen1”.
- hero: “Zaguán de finca de barrio, reja y hormigón.”
- amparo: “Fachada y patio de Calle Amparo 14, Usera.”
- albufera: “Bloque en la avenida de la Albufera, terraza en quinto.”
- yeserias: “Bajo en el paseo de Yeserías, umbral a la calle.”
- sicilia: “Portal de Calle Sicilia 9, Entrevías.”
- alegria: “Ático con terraza en Calle de la Alegría, Orcasur.”
11. Piezas que no se escriben
| No | Motivo |
|---|---|
| “Te llamamos en 5 minutos” | S2 / H2 / H3 |
| “Reserva tu visita” / “Pedir cita” | H2 · Comparar ≠ reservar |
| “Ya ha venido gente” como hecho de publicación | Publicado ≠ visitado |
| “Has ofertado 950 €” | Criterio ≠ oferta |
| Ranking de los más vistos | Likes, otro producto |
| Lorem / “lorem ipsum piso” | Prohibido en el case |
| Declaraciones inventadas de una agencia real | Verdad de catálogo |
10-accessibility.md
10 — Accesibilidad — ZAGUÁN
Objetivo: WCAG 2.2 AA. No se declara conformidad legal certificada.
Este documento cubre la UI (wizard, comparador, ficha, mesa). No audita la accesibilidad física de las fincas seed (ascensor, umbral, baño). [SUPUESTO]
1. Decisiones
| Tema | Decisión |
|---|---|
| Tipo UI | Sora ≥16px body |
| Display | Instrument Serif no se usa por debajo de 20px |
| Contraste | Grafito #1F1D1B sobre #E3E1DC / #F3F1EC |
| CTA óxido | Texto caliza sobre #B54A2A |
| Badge PUBLISHED | Texto caliza ≥14px bold o óxido sobre caliza |
| Foco | Outline 2px óxido, offset 2px, visible (no outline-none global) |
| Wizard | Chips son <button> con aria-pressed; pasos son <nav> |
| Comparador desktop | 3 <article> o 3 <a> en grid; no tabla de 8 columnas |
| Comparador móvil | Scroll-x snap; cada columna ≥280px; no apilar datos ilegibles |
| Imágenes | alt = dirección de la finca |
| Form | Labels visibles, no placeholder-only |
| Errores | Texto, no solo color |
| Mobile | Targets ≥44px (chips, Siguiente, toggles) |
| Movimiento | Fade 160ms; prefers-reduced-motion: reduce → 0 |
| Live regions | aria-live="polite" en empty/error del comparador |
2. Contraste (comprobación de diseño)
| Par | Uso | Nota |
|---|---|---|
#1F1D1B / #E3E1DC | Body sobre hormigón | Pasa AA y AAA cuerpo |
#1F1D1B / #F3F1EC | Body sobre caliza | Pasa AA y AAA |
#F3F1EC / #B54A2A | CTA / PUBLISHED | Pasa AA para texto ≥14px bold |
#6A6560 / #E3E1DC | Meta hierro | Verificar ≥13px; no usar en 11px |
#6A6560 como texto 12px sobre junta | — | Prohibido para body |
| Paso idle hierro 50% | Solo si no es el único indicador | + “2 de 3” textual |
3. Teclado
| Flujo | Orden |
|---|---|
| Paso 1 | Wordmark → chips zona → tipo → Siguiente → Soy agente |
| Paso 2 | Wordmark → pasos → tope → hab → Atrás / Siguiente |
| Paso 3 | Toggles → Atrás / Comparar |
| Comparador | Resumen criterio → col 1 → col 2 → col 3 |
| Ficha | Volver → contenido |
| Login | Email → password → submit |
| Mesa | Nueva → filas (Publicar / ver) → Salir |
- Enter en chip = selecciona (
aria-pressed). - Enter en columna = navegar a ficha.
- Escape no cierra el wizard (no es modal).
- Focus trap: no hay modal Must.
4. Semántica
| Pieza | Markup |
|---|---|
| Chrome pasos | <nav aria-label="Criterio"> · activo aria-current="step" |
| Chips zona | role="group" + aria-label="Zona" |
| Tope | <label for="maxRent"> + input type="number" |
| Musts | <fieldset> + <legend> “Qué no negocias” |
| Comparador | <section aria-label="Tres fincas"> |
| Columna | un <a> por finca; hueco vacío no es enlace |
| Disclaimer | No es alert; es texto normal + borde |
| Precio | 890 € al mes anunciado con unidad, no solo “890” |
| Badges | texto visible; no solo icono |
| Skeletons | aria-busy="true" en el contenedor; aria-hidden en placeholders |
5. Lector de pantalla — copy
| Situación | Anuncio |
|---|---|
| Chip | “Usera, pulsado” / “Vallecas, no pulsado” |
| Columna | “Calle Amparo 14, 3º D, 890 euros al mes, 68 metros, tercero sin ascensor, 42 euros de comunidad, patio” |
| Empty | “Ninguna finca encaja con este criterio.” |
| Error | Anuncio en live region; foco al banner |
| Success | “Publicada. Publicado no es visitado.” |
6. Producto vs finca física
| Lo que ZAGUÁN puede hacer | Lo que no afirma |
|---|---|
| Decir si hay ascensor, planta, m² | Que el 3º de Amparo sea accesible de hecho |
| Wizard usable con teclado y zoom 200% | Que el local de Marcelo Usera tenga rampa |
| Extra “mascotas OK” | Condiciones de la comunidad real |
[DECISIÓN] v1 no incluye auditoría a11y de sede. El campo elevator es dato de catálogo, no un certificado.
7. Riesgos
| Riesgo | Mitigación |
|---|---|
| 3 columnas en 390 | UI mobile: scroll-x snap; no comprimir a 100px |
| Instrument Serif en meta 12px | Prohibido; meta = Sora 13 |
| Óxido como único estado de paso | + aria-current="step" + “2 de 3” |
| Foto sin alt | Checklist seed: 5 alts + hero |
| Toggle must leído como “sin X” cuando está OFF | Copy: “apagado = te da igual” |
| Slider de renta sin input | Input numérico es la fuente; slider es Should |
8. QA a11y (mínimo)
| # | Prueba | Pasa si |
|---|---|---|
| 1 | Teclado paso 1 → 2 → 3 → comparar → ficha | Sin trampa; foco visible |
| 2 | Teclado login → mesa → PATCH | Acciones alcanzables |
| 3 | Zoom 200% móvil 390 | Chips usables; no solapa inputs |
| 4 | Lighthouse a11y ≥ 90 en / | Sin contrast fails de tokens |
| 5 | VoiceOver/NVDA: columna anunciada | Precio y planta comprensibles |
| 6 | prefers-reduced-motion | Sin stagger ni slide |
| 7 | Contraste badges DRAFT / PUBLISHED | Texto bold ≥14 |
9. Criterios de aceptación
- Ningún control del wizard o CTA mide menos de 44×44 CSS px.
- El disclaimer no depende del color óxido para ser entendido.
- Las 5 fotos seed + hero tienen
altno vacío y no genérico. - El foco no se pierde al reintentar un error de red.
- En 390 el comparador no obliga a leer 3 columnas a la vez: hay snap.
- Un DRAFT no se anuncia en el comparador.
11-privacy-security.md
11 — Privacidad y seguridad — ZAGUÁN
1. Datos que existen
| Dato | Dónde | Base / nota |
|---|---|---|
| Email y hash de la agente | users | Contrato interno de mesa |
| Fincas (dirección, renta, fotos) | listings (público si PUBLISHED) | Catálogo de producto; seed demo |
| Criterio de compare | Body de POST; comparisons opcional | Anónimo; no se liga a persona |
| Sesión JWT | Authorization + sessions? | Solo AGENTE |
No se pide cuenta al buscador. No hay teléfono, email ni nombre de Marcos en producto. No hay tracking de menores. Las fotos seed no identifican vecinos reales.
2. Minimización
| Qué no se recoge en v1 | Por qué |
|---|---|
| Teléfono / email del buscador | S2 / H3 · no lead |
| DNI / renta acreditada | Criterio ≠ oferta; no hay scoring |
| Geolocalización del dispositivo | Zona es un chip, no GPS |
| Historial de visitas a fincas físicas | Publicado ≠ visitado |
| Fotos subidas por el buscador | No hay UGC |
Comparison persistida (si se activa) guarda criterio + slugs, no IP como identidad. [DECISIÓN] Si se persiste, retención corta (p. ej. 30 días) en L+1; v1 puede ser efímera.
3. Seguridad L1
| Control | Spec |
|---|---|
| Auth | JWT Bearer · role === AGENT |
| Password | bcrypt cost 12; nunca en response |
| DRAFT | Nunca en GET /api/listings ni :slug (404) |
| Escritura | POST / PATCH listings y GET mesa → 401 sin token |
| Rate limit | /auth/login (anti stuffing; Should si no llega al slice) |
| CORS | Origen http://localhost:4200 en local |
| Secretos | Solo .env (no git) |
| Cover allowlist | Filenames seed; no URL arbitraria en POST |
next post-login | Paths relativos allowlist (/mesa, /mesa/nueva) |
No se declara cumplimiento RGPD definitivo. Sí: minimización y derecho a borrar la cuenta de agente (manual L1).
4. Superficies de riesgo
| Riesgo | Mitigación v1 |
|---|---|
| Enumeración de slugs DRAFT | 404 idéntico a “no existe”; no 403 |
| JWT en localStorage | Aceptable en demo; no datos de buscador al lado |
| Criterio en sessionStorage | No es PII; se limpia al wordmark → / |
Leftover pieces / cuentas MARGEN en DB | I01 de build: schema propio, no mezclar |
| Fotos de interiores con personas | Seed: fachadas / umbrales; no caras identificables |
5. Roles y autorización (recordatorio)
| Recurso | Anónimo | AGENT |
|---|---|---|
| GET listings / compare / :slug PUBLISHED | Sí | Sí |
| GET :slug DRAFT | 404 | 404 (público); mesa sí |
| GET mesa / POST / PATCH | 401 | Sí |
| Login | Sí (emite token si vale) | — |
Inés y Marcos no tienen fila en users.
6. Criterios de aceptación
- Response de login no incluye
passwordnipassword_hash. - GET público de
merced-2es 404. - GET
/api/mesa/listingssin Bearer es 401, no 200 []. - No hay formulario público que pida teléfono o email del buscador.
.envno se commitea.
12-analytics.md
12 — Analítica — ZAGUÁN
North star: % de sesiones que llegan a /comparar con ≥1 columna y abren ≥1 ficha (compare_view + listing_open).
Instrumentación modelo. No hay proveedor conectado en v1 salvo que el build añada un logger local. [DECISIÓN] No se inventan cifras de conversión.
1. Eventos
| Evento | Cuándo | Props mín. |
|---|---|---|
wizard_step | Completa paso 1 / 2 / 3 | step, zona?, maxRent? |
compare_view | Render de /comparar | matches (0–3) |
listing_open | Apertura de ficha | slug |
compare_empty | 0 matches | criteria |
agent_login | JWT ok | — |
listing_publish | DRAFT → PUBLISHED | slug |
error_banner | UI-11 | surface |
2. Qué no se mide
| No evento | Motivo |
|---|---|
signup_click / lead_submit | No existe CTA |
visit_booked | Comparar ≠ reservar |
offer_sent | Criterio ≠ oferta |
like / favorite | S4 |
| Identidad de Marcos | H3 · anónimo |
Hipótesis H2: compare_view sin visit_click (el CTA no existe). Si en un test de 5 usuarios alguien busca el botón, se anota en research futuro, no como KPI inventado.
3. Funnel modelo (BUSCADOR)
paso 1 → paso 2 → paso 3 → compare_view → listing_open
Activación = compare_view con matches ≥ 1.
Completud = listing_open en la misma sesión.
AGENTE: agent_login → listing_publish.
4. Integridad (producto, no vanidad)
| Señal | Cómo se observa |
|---|---|
| DRAFT no filtra | GET /listings sin merced-2 (QA, no analytics) |
| Expectativa de visita | Comentarios cualitativos; cero evento de reserva |
| Salud | 4xx/5xx de listings/compare |
5. Privacidad de analítica
Sin PII. Criterio se puede agregar (zona=USERA, maxRent en buckets de 50 €). No se guarda IP como usuario. Ver doc 11.
6. Criterios de aceptación (si se instrumenta)
- Los 7 eventos de §1 tienen nombre estable.
- No se dispara ningún evento de lead / visita / oferta.
- Un bloqueador de cookies no rompe el wizard (analítica no es Must de runtime).
13-qa-test-plan.md
13 — Plan de pruebas — ZAGUÁN
1. Smoke obligatorio (D-P1-06)
| # | Acción | Esperado |
|---|---|---|
| 1 | GET /api/listings | 200 · length = 5 · slugs PUBLISHED presentes · ningún DRAFT merced-2 |
| 2 | GET /api/listings/amparo-14 | 200 · address/rent/sqm/cover amparo.jpg |
| 3 | POST /api/auth/login lucia@zaguan.barrio / password123 | 200 · accessToken · user.role=AGENT |
| 4 | GET /api/mesa/listings con Bearer | 200 · incluye DRAFT merced-2 |
| 5 | ng build o serve web | Web arranca o build OK |
[COMPROBADO] Smoke = listings + ficha + login JWT + mesa.
2. Casos funcionales
| ID | Caso | Esperado |
|---|---|---|
| S1 | Slugs seed publicados | GET listings incluye amparo-14, albufera-212, yeserias-8, sicilia-9, alegria-3 |
| S2 | DRAFT no sale en público | merced-2 ausente del array público |
| S3 | Slug DRAFT | GET /api/listings/merced-2 404 |
| S4 | Slug inexistente | 404 |
| S5 | Compare Marcos | POST { zona: USERA, tipo: ALQUILER, maxRent: 950, rooms: 2 } · incluye amparo-14 y yeserias-8 · rent ≤ 950 · ≤ 3 items |
| S6 | Compare UI | /comparar muestra columnas (precio, m², planta, gastos, extras) y disclaimer; cero CTA Reservar |
| S7 | Ficha AC | dirección, precio, planta, gastos, “ZAGUÁN no llama” |
| S8 | Wizard es la home | / es paso 1 (zona+tipo); no hero 2-col |
| S9 | Mesa sin JWT | 401 / redirect login |
| S10 | POST listing | 201 · status=DRAFT · slug kebab |
| S11 | PATCH PUBLISHED | 200 · la finca aparece en GET listings |
| S12 | PATCH DRAFT (retirar) | 200 · desaparece de público · slug público 404 |
| S13 | Login malo | 401 · mensaje en form |
| S14 | Empty compare (VENTA o tope 400) | UI-10 |
| S15 | API caída | UI-11 + Reintentar conserva la vista |
| S16 | Loading | Skeleton 3 cols antes del primer paint de compare |
| S17 | Mobile 390 | Wizard 3 pasos + compare snap; CTA ≥44px; no hero 2-col |
| S18 | Fotos seed | 5 covers resuelven a assets/listings/* |
| S19 | Password no viaja de vuelta | login response sin hash |
| S20 | Body POST listing | acepta address, zona, rent, sqm, rooms, musts, body |
| S21 | Success | /mesa/ok/:slug copy “Publicado no es visitado” |
| S22 | Público no lista mesa | GET mesa sin token 401 (no 200 []) |
| S23 | Query GET listings | ?zona=USERA&maxRent=950 no devuelve sicilia-9 (1050) |
| S24 | Paso 2 sin zona | redirect / |
| S25 | Microcopy ×4 | comparar≠reservar · criterio≠oferta · publicado≠visitado · no llama |
3. Datos de prueba
| Uso | Valor |
|---|---|
| AGENTE | lucia@zaguan.barrio / password123 |
| BUSCADOR | Marcos Vidal · sin cuenta |
| Finca feliz | amparo-14 |
| Finca DRAFT | merced-2 |
| Compare feliz | USERA · ALQUILER · 950 · 2 hab · musts OFF |
| Compare vacío | VENTA, o maxRent 400 |
4. Regresión de diversidad / craft
| Check | Falla si |
|---|---|
| Home es wizard | Hay hero 2-col + 3 cards de marca (AP-12) o lookbook (MARGEN) o tabla (CESTA) |
| Shell es S-WIZ | Hay masthead de periódico, rail, dock flotante o command search como nav primaria |
| Copy honesto | Success dice “ya hay visitas” o compare dice “reserva” o paso 2 dice “tu oferta” |
| Paleta | Se cuelan cadmio MARGEN / musgo CESTA / cromo RONDA / chalky PIZARRA |
| Tipo | Cormorant / Public Sans / Newsreader / Outfit / Spectral / Manrope / Zilla / Mulish / Lora / Karla |
| Rutas | Aparecen /visita, /agenda, /piezas, /necesidades, /inbox |
| Ground | Página en caliza #F3F1EC en vez de hormigón #E3E1DC |
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 (3 pasos, 3 columnas, disclaimer de reserva, ausencia de signup buscador, DRAFT 404).
6. Criterios de salida QA v1
- Smoke §1 en verde.
- S2, S3, S5, S6, S8, S11, S17, S21, S22, S25 en verde.
- Cero blockers de copy deshonesto (reservar / ofertar / te llamamos / visitado).
- Sin
.enven el repo.
7. Fuera de este plan
Playwright e2e completo, carga, fuzzing, auditoría de una finca o agencia reales.
14-dev-handoff.md
14 — Handoff desarrollo — ZAGUÁN
1. Arranque
cd /Users/cristian/orca/zaguan-app
# apps independientes (npm --prefix o pnpm --filter)
# apps/api/.env
# DATABASE_URL= # Neon curly-recipe-23023272
# JWT_SECRET=
# PORT=3018
# migrate / ensureSchema + seed
# api → http://localhost:3018
# web → http://localhost:4200
Demo: lucia@zaguan.barrio / password123.
2. Paridad Paper
| Debe verse en Angular | Artboard |
|---|---|
| Tokens rusted + Instrument Serif / Sora | UI-00 |
| Wizard paso 1 zona | UI-01 |
| Wizard paso 2 presupuesto | UI-02 |
| Wizard paso 3 musts | UI-03 |
| Comparador 3 cols | UI-04 |
| Ficha + “no llama” | UI-05 |
| Login AGENTE | UI-06 |
| Mesa DRAFT / PUBLISHED | UI-07 |
| Alta de finca | UI-08 |
| Success “publicado ≠ visitado” | UI-09 |
| Empty / error | UI-10, UI-11 |
Loading (skeleton 3 cols) y mobile wizard (~390) se implementan sobre las mismas rutas; spec en docs 16–17.
No clonar MARGEN (lookbook + masthead), CESTA (tabla + rail), RONDA (feed + dock), CANTERA (parte + dock) ni PIZARRA (command search + inscripción).
Paper se construye en paralelo; los nombres de artboard son el inventario planificado (doc 00).
3. Contratos
| Tema | Contrato |
|---|---|
| Login | { accessToken, user } camelCase (no access_token) |
| Listado | GET /api/listings → PUBLISHED; query zona, maxRent, rooms, elevator, terrace, pets, renovation |
| Compare | POST /api/compare → { criteria, listings } (≤3) |
| Ficha | GET /api/listings/:slug · 404 si DRAFT |
| Mesa | GET /api/mesa/listings Bearer |
| Alta | POST /api/listings campos de doc 05 |
| Status | PATCH /api/listings/:id { status } |
| Status enum | DRAFT | PUBLISHED |
| Zona enum | USERA | VALLECAS | ENTREVIAS | ORCASUR |
| Tipo enum | ALQUILER | VENTA |
| Role | AGENT (no EDITOR) |
Campos JSON en camelCase hacia el web aunque SQL sea snake_case. [DECISIÓN]
4. Mapa de rutas web
| Path | Guard cliente | Página |
|---|---|---|
/ | no | Wizard paso 1 |
/criterio/presupuesto | soft (zona) | Paso 2 |
/criterio/musts | soft (tope) | Paso 3 |
/comparar | soft (criterio) | Comparador |
/fincas/:slug | no | Ficha |
/login | no | Login |
/mesa | soft (redirect) | Mesa |
/mesa/nueva | soft | Alta |
/mesa/ok/:slug | soft | Success |
Soft guard público: si falta el paso anterior, /.
Soft guard mesa: si no hay token, /login?next=. La API es la autoridad.
[COMPROBADO] Solo estas rutas. No implementar /visita, /agenda, /piezas, /mesa/:id.
5. Assets
Copiar desde el case:
2026-08-18-zaguan/assets/hero.jpg
2026-08-18-zaguan/assets/listings/amparo.jpg
2026-08-18-zaguan/assets/listings/albufera.jpg
2026-08-18-zaguan/assets/listings/yeserias.jpg
2026-08-18-zaguan/assets/listings/sicilia.jpg
2026-08-18-zaguan/assets/listings/alegria.jpg
→ apps/web/public/assets/listings/ (hero a public/assets/)
cover del seed = filename (amparo.jpg, …).
Quitar leftover public/assets/{merienda,colegio,piscina,biblioteca}.jpg (semanario MARGEN) si sigue en el repo.
6. DoD implementación L1
| Check | OK |
|---|---|
| API sirve :3018 | |
| Seed 1 AGENTE + 5 PUBLISHED + 1 DRAFT | |
| Smoke GET listings + GET slug + login + GET mesa | |
| POST compare devuelve ≤3 PUBLISHED | |
| JWT protege mesa / POST / PATCH | |
| DRAFT 404 en público | |
| Web wizard 3 pasos + compare + ficha + mesa + alta + success | |
| Microcopy ×4 | |
| Tokens Tailwind = paleta brief | |
| Home es paso 1 (no leftover lookbook / tabla / feed) | |
ng build o equivalente OK | |
| README del repo app con credencial |
7. No hacer
| Prohibido | Motivo |
|---|---|
| npm workspaces | AP-06 |
Commit de .env | Secreto |
| Prometer visita o llamada | Microcopy duro / AP-09 |
Estado SEATED / ENROLLED / CONTACTED / RECEIVED | Vocabulario de otros días |
Role EDITOR | Eso es MARGEN |
| Search bar o mapa como home | Rompe H-WIZARD |
| Hero 2-col + 3 cards | AP-12 |
| Lookbook + masthead | MARGEN |
| Tabla de necesidades como home | CESTA |
| Feed + dock | RONDA |
image_gen como captura de producto | D-P0-09 |
Body POST con kicker / issueId / needSlug | Leftover |
8. Contactos de diseño (artefactos)
| Artefacto | Path |
|---|---|
| Brief | ux-projects/2026-08-18-zaguan/docs/00-day-brief.md |
| Paper | https://app.paper.design/file/01M09V13JEANPFFD6DS1RCN29G |
| Datos | docs/05-data-model.md |
| Flujos | docs/04-user-flows.md |
| IX | docs/16-interaction-specs.md |
| Build | docs/20-implementation.md |
15-roadmap.md
15 — Roadmap — ZAGUÁN
Este documento no es un cajón de trabajo L1 a medias.
El vertical slice L1 del brief está especificado como done-scope (docs 01, 18, 19).
Aquí solo viven ideas de L+1 / L3 y pulido posterior.
1. Hecho en L1 (esta ejecución documental)
| Entrega | Evidencia |
|---|---|
| Definición producto + terna | docs 00-brief, 01 |
| Personas Marcos / Lucía / Inés, JTBD, journey | docs 02 |
| IA wizard-first + chrome S-WIZ | docs 03 |
| Flujos F-CMP + mesa AGENTE | docs 04 |
| Modelo users / listings / comparisons + seed | docs 05 |
| Stack Angular/Nest/Neon/JWT :3018 | docs 06, 20 |
| Dirección rusted + DS | docs 07, 08 |
| Copy Comparar ≠ reservar · Criterio ≠ oferta · Publicado ≠ visitado · ZAGUÁN no llama | docs 09 |
| Paper ref 12 UX + 12 UI | docs 00-paper-reference |
| Suite 00–20 + README + executive | case folder |
2. Implementación L1 (misma complejidad, código)
Contrato en docs 14 y 20. No es deuda silenciosa del case documental: es el build del repo zaguan-app. El alcance L1 ya está cerrado en spec; el código alinea leftover (issues / pieces / events) al contrato.
Eso no se reabre aquí como “próximo trimestre de producto”.
3. L+1 (misma familia, subida de pulido)
| Ítem | Notas |
|---|---|
| Persistencia estable de Comparison + replay | Home sigue siendo el paso 1 |
| Preview de DRAFT con token | No 200 público |
| Relajar musts con mismatch badges | Sigue sin inventar fincas |
| Rate limit login | Anti stuffing |
| Playwright S1–S25 | CI |
| Despublicar con confirm | PATCH DRAFT |
| API URL por environment | Deploy |
prefers-reduced-motion verificado | a11y |
Foto para merced-2 cuando se publique | Allowlist |
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-agencia / varios locales
- Cuenta BUSCADOR y favoritos
- Calendario de visitas / F-BOOK
- Lead “te llamamos”
- Pasarela / señal / contrato
- Mapa como home
- Search-first (rompería terna)
- CRM de captación (40 estados)
5. Explícitamente fuera (no backlog disfrazado)
| Ítem | Por qué |
|---|---|
| Reserva / slot / confirmación de visita | H2 · ≠ F-BOOK |
| Lookbook editorial / agenda | ≠ MARGEN |
| Tabla de necesidades / compromiso | ≠ CESTA |
| Parte de obra / wizard de oficio | ≠ CANTERA |
| Feed + dock | ≠ RONDA |
| App nativa | Fuera de stack daily |
| Hero 2-col + 3 cards | AP-12 |
6. Orden de ataque si hay continuidad de código
- Contratos 05–06 + seed 05 + smoke 13 (alinear leftover)
- Web paridad UI-00…11
- Preview token + mismatch badges (L+1)
- Rate limit si hay tráfico
- Evaluar brief L3 (no parche silencioso)
16-interaction-specs.md
16 — Especificación de interacción — ZAGUÁN
1. Convenciones
| Token | Valor |
|---|---|
| Duración corta | 120–160ms |
| Easing | ease-out |
| Reduced motion | 0ms |
| Target | ≥44×44 |
| Pending | control disabled + aria-busy |
Loading y mobile wizard (variante 390 de UI-01…03 y skeleton de UI-04) viven en este doc; no tienen artboard extra en el inventario de 12. [DECISIÓN]
2. Chrome de pasos (S-WIZ)
| Gesto | Resultado |
|---|---|
| Tap wordmark | / ; reinicia criterio (confirm no Must) |
| Tap 1 Zona | / ; aria-current="step" |
| Tap 2 si hay zona | /criterio/presupuesto |
| Tap 2 sin zona | no navega; el paso se ve bloqueado |
| Tap 3 si hay tope | /criterio/musts |
| Tap 3 sin tope | no navega |
| Scroll del paso | Chrome puede ser sticky (Should); no se esconde |
| Teclado | Tab: wordmark → pasos alcanzables → contenido |
Estado activo: label óxido + filete 2px óxido bajo el ítem.
Estado default: hierro.
Estado bloqueado: hierro 50%, no es enlace.
Desktop: pasos a la derecha o centrados.
390: wordmark + “1 de 3 · Zona”. Los tres labels caben si se compactan a 1 / 2 / 3.
3. Paso 1 · zona + tipo (UI-01 · mobile variante)
| Gesto | Resultado |
|---|---|
| Tap chip zona | aria-pressed único (radio); persiste zona |
| Tap tipo | ALQUILER / VENTA; default ALQUILER |
| Tap Siguiente | si hay zona+tipo → paso 2; si no, inline |
| Tap Soy agente | /login |
| Primera carga | Form visible; no skeleton de marca |
AC: chips y titular están en el viewport inicial a 390 y a 1280 (sin hero de 60vh).
390: chips en wrap 2×2; CTA full-width h-44 al fondo del paso, no tapado por teclado (el paso 1 no abre teclado).
4. Paso 2 · presupuesto (UI-02)
| Gesto | Resultado |
|---|---|
| Input tope | dígitos; suffix “€/mes”; disclaimer visible |
| Chip 1 / 2 / 3+ | radio rooms |
| Slider (Should) | sincroniza el input; no sustituye al input |
| Siguiente | valida maxRent ≥ 500 y rooms |
| Atrás | / conservando zona |
El valor del tope nunca se etiqueta “oferta” ni “puja”.
5. Paso 3 · musts (UI-03)
| Gesto | Resultado |
|---|---|
| Toggle | ON = filtro duro; OFF = no filtra. aria-checked |
| Comparar | POST /api/compare → /comparar (pending en el botón) |
| Atrás | /criterio/presupuesto |
| Error de red en POST | banner UI-11 en el propio paso; no navegar |
Doble tap Comparar: ignorado mientras pending.
6. Comparador (UI-04) + loading
| Gesto | Resultado |
|---|---|
| Primera carga | Loading: 3 skeletons caliza 4:3 + 4 barras junta; aria-busy |
| 200 con 1–3 | columnas; huecos dashed si < 3 |
| 200 con 0 | UI-10 empty + CTA “Afloja un must” → paso 3 |
| Error 5xx / red | UI-11 + Reintentar (repite POST) |
| Tap columna | /fincas/:slug (columna completa clicable) |
| Tap hueco vacío | nada |
| Hover desktop | Rule 1px grafito; no elevación |
| Pull-to-refresh | No en v1 |
AC: precio, m², planta, gastos, extras visibles en cada columna sin hover. Disclaimer “Comparar no es reservar” sobre el fold desktop.
390: scroll-x scroll-snap-type: x mandatory; cada columna ~82vw; dots o “1/3” Sora 12. Criterio resumen sticky arriba.
Orden visual = score del API. No “destacados” por likes.
7. Ficha de finca (UI-05)
| Gesto | Resultado |
|---|---|
| Scroll | Lectura normal; sin scroll-jacking |
| Tap ← Las tres fincas | /comparar (criterio intacto) |
| Foto | No lightbox v1 |
| Selección de texto | Permitida (anotar dirección es backstage) |
Sin doble tap like. Sin “llamar ahora”. Sin WhatsApp click-to-chat.
8. Login (UI-06)
| Gesto | Resultado |
|---|---|
| Submit | POST login; pending en botón |
| 200 | Guarda token + user; navega next sanitizado o /mesa |
| 401 | Texto bajo el form; password no se limpia |
| Enter | Submit |
next permitido: paths que empiezan por / y no // ni http. Allowlist: /mesa, /mesa/nueva.
9. Mesa (UI-07)
| Gesto | Resultado |
|---|---|
| Sin token | Redirect login |
| Tap Nueva finca | /mesa/nueva |
| Tap Publicar en fila DRAFT | PATCH PUBLISHED; badge cambia |
| Tap título | [DECISIÓN] no hay /mesa/:id; el título no navega; “ver” (link a ficha si PUBLISHED) sí |
| Empty | Titular + CTA Nueva |
| Error 401 mid-session | Redirect login |
| Error red | Banner + Reintentar |
No swipe-to-archive. No kanban.
Optimistic UI en Publicar: badge pasa a Publicado; si PATCH falla, rollback + toast error.
Publicar no anima unas llaves ni un “+1 visita”.
10. Alta de finca (UI-08)
| Campo | Interacción |
|---|---|
| address / title | text |
| zona | select 4 valores |
| tipo | select ALQUILER / VENTA |
| rent, sqm, rooms, expenses | number |
| floorLabel | text (3º D) |
| musts | 4 checkboxes |
| cover | select filenames seed (preview 4:3); opcional en DRAFT |
| body | textarea 6+ filas |
| Gesto | Resultado |
|---|---|
| Guardar borrador | POST DRAFT; redirect /mesa o /mesa/ok/:slug con copy de borrador |
| Publicar | POST + PATCH PUBLISHED → /mesa/ok/:slug |
| Submit inválido | Foco al primer campo error; no POST |
| Error red | Botón Reintentar; no limpia campos |
| Doble tap | Ignorado mientras pending |
Submit deshabilitado si address < 4, body < 40, rent vacío o zona vacía.
[DECISIÓN] Dos botones visibles. Primario = Publicar. Secundario = Guardar borrador. El primario no se llama “Poner en visita”.
11. Success (UI-09)
| Gesto | Resultado |
|---|---|
| CTA primario | Ver la finca pública /fincas/:slug |
| CTA secundario | Volver a la mesa /mesa |
| Copy | Visible “Publicado no es visitado” |
Sin confetti. Sin “¡ya hay visitas!”.
12. Empty (UI-10) · Error (UI-11) · Loading · Mobile
| Estado | Interacción |
|---|---|
| Empty compare | Titular + 1 línea + CTA a paso 3 (y secundario a paso 2) |
| Empty mesa | Titular + CTA Nueva |
| Error | Banner + Reintentar; foco al banner; no pierde criterio ni form |
| Loading compare | 3 skeletons; no spinner centrado de marca |
| Loading mesa | 5 filas junta |
| Mobile 390 wizard | Un paso = una pantalla; CTA h-44; chrome “N de 3” |
| Mobile 390 compare | snap-x; no 3 cols de 100px |
| Mobile 390 ficha | cover 4:3 full; disclaimer visible |
13. Motion
| Superficie | Spec |
|---|---|
| Cambio de paso | Fade 160ms o translateX 8px (off si reduced-motion) |
| Columnas compare | Fade 160ms, stagger ≤30ms (off si reduced-motion) |
| Cambio de badge | Crossfade 120ms |
| Chrome | Sin hide-on-scroll v1 |
| Skeletons | Estáticos o pulso 1.2s opacity 0.6–1; off si reduced-motion |
Prohibido: scroll-jacking, page transitions de 400ms, parallax de portal, like animation, llaves girando.
14. Criterios de aceptación de interacción
- Un BUSCADOR en 390px llega de paso 1 a comparar en 3 taps de CTA (Siguiente ×2 + Comparar).
- Un AGENTE en 390px publica un DRAFT en ≤ 3 taps desde mesa (acción en fila).
- Ningún submit permite doble POST.
- El chrome no cubre el H1 del paso (spacer si sticky).
- Reduced-motion elimina stagger y slide.
- La home no es hero 2-col (AP-12).
- En 390 el recorte 4:3 de la primera columna es visible (snap).
/compararno presenta ningún control de reserva ni “te llamamos”.- El loading de compare es skeleton de 3 columnas, no un logo.
- Paso 2 muestra “criterio, no una oferta” sin scroll en desktop.
17-prototype-map.md
17 — Mapa de prototipo — ZAGUÁN
Paper no es clicable vía MCP. El prototipo vivo es la app Angular.
Los artboards de Paper se construyen en paralelo; esta tabla es el contrato de paridad.
| Ruta | Paper | Rol |
|---|---|---|
/ | UI-01 (+ mobile 390, spec 16) | BUSCADOR |
/criterio/presupuesto | UI-02 | BUSCADOR |
/criterio/musts | UI-03 | BUSCADOR |
/comparar | UI-04, UI-10, UI-11 (+ loading, spec 16) | BUSCADOR |
/fincas/:slug | UI-05 | BUSCADOR / Inés |
/login | UI-06 | AGENTE |
/mesa | UI-07 | AGENTE |
/mesa/nueva | UI-08 | AGENTE |
/mesa/ok/:slug | UI-09 | AGENTE |
Proceso: UX-00…UX-11. Tokens: UI-00.
Loading y mobile no tienen board propio en el inventario de 12: se prototipan como estados / variantes de UI-01 (390) y UI-04 (skeleton). Ver doc 16.
Recorrido demo (guión)
- Abrir
/— paso 1 ¿Por dónde entras?; chips Usera / Vallecas / Entrevías / Orcasur. - Elegir Usera + Alquiler → Siguiente.
- Paso 2: tope 950 €, 2 hab. Leer “Esto es un criterio, no una oferta.”
- Paso 3: musts OFF → Comparar estas tres.
/comparar— columnas Amparo 14 (890) y Yeserías 8 (820); tercera hueco o vacía. Leer “Comparar no es reservar.”- Tocar Calle Amparo 14, 3º D — ficha, patio, “ZAGUÁN no llama.”
- Comprobar que no hay botón Reservar ni Te llamamos.
/login—lucia@zaguan.barrio/password123./mesa— ver DRAFT Calle de la Merced 2.- Nueva finca o Publicar el DRAFT →
/mesa/ok/:slug— “Publicado no es visitado.” - Volver a comparar en otra sesión: el DRAFT no está; lo publicado sí.
Estados en el mismo prototipo
| Estado | Cómo provocarlo |
|---|---|
| Empty compare | tipo VENTA, o maxRent 400, o musts imposibles (ascensor+terraza+reforma+mascotas en Usera 2 hab 950) |
| Empty mesa | lista vacía autenticada |
| Error | API caída / 5xx |
| Loading | throttle de red en POST compare |
| 404 DRAFT | /fincas/merced-2 sin publicar |
| Mobile wizard | 390 × 844 en /, /criterio/presupuesto, /criterio/musts |
| Mobile compare | 390 × 844 en /comparar (snap-x) |
| Hueco 3.ª col | criterio Marcos (2 matches) |
Fuera del prototipo clicable
- Paper artboards son estáticos.
- Llamada de Lucía a Inés / juego de llaves (backstage).
- Visita en el zaguán físico.
- Reserva de visita (no existe).
- Lead / teléfono.
- Mapa.
- Portal multi-agencia.
18-completeness-audit.md
18 — Auditoría de completitud — ZAGUÁN (2026-08-18)
1. Alcance auditado
Vertical slice L1: wizard de criterios + comparador de 3 fincas + ficha + mesa AGENTE JWT, con docs, referencia Paper, contratos Angular + Nest + Neon.
Este encargo cubre la suite documental. Paper hi-fi y el repo zaguan-app pueden existir en paralelo; la auditoría marca lo que esta suite cierra. El day brief no se ha sobrescrito.
2. Checklist CRON / ALS-2
| Requisito | Estado | Evidencia |
|---|---|---|
| Diversidad sector/tipo/nivel | OK | Inmobiliaria de barrio L1; no galería; no tabla; no feed; no parte de obra |
| Terna S/H/F 0 códigos = N−1 · ≥2 vs N−2 | OK | S-WIZ · H-WIZARD · F-CMP vs MARGEN S-TOP · H-GALLERY · F-STORY |
| Day brief + anti-patrones | OK | docs/00-day-brief.md (no sobrescrito) |
| Paper ≥12 UX + ≥10 UI | OK | UX-count 12 · UI-count 12 · file 01M09V13JEANPFFD6DS1RCN29G |
| Docs 00–20 | OK | suite en docs/ + executive + README |
| JWT AGENTE | OK | Role AGENT · D-P1-05 |
| API + seed + Neon | OK | contratos curly-recipe-23023272, port 3018 |
| Web tokens | OK | Instrument Serif + Sora · paleta rusted |
| Wizard-first (no AP-12) | OK | UI-01 / docs 03, 07, 08 |
| Hipótesis no fake field | OK | etiquetas en doc 02 |
| Copy ×4 irrenunciable | OK | docs 01, 09, 16 |
| Ground G-CONCRETE | OK | #E3E1DC · no G-GALLERY / G-MOSS / G-CHROME / G-WATER / G-MUNICIPAL |
3. Cobertura funcional
| Feature brief | Spec | UI Paper | API | Docs |
|---|---|---|---|---|
| Wizard 3 pasos | Sí | UI-01, 02, 03 | criterio cliente + POST compare | 03, 04, 16 |
| Comparador 3 cols (precio, m², planta, gastos, extras) | Sí | UI-04 | POST compare | 04, 05 |
| Ficha de finca | Sí | UI-05 | GET :slug | 05, 09 |
| Login AGENTE | Sí | UI-06 | POST login | 04, 06 |
| Mesa DRAFT → PUBLISHED | Sí | UI-07 | GET mesa + PATCH | 04, 05 |
| Alta + success | Sí | UI-08, UI-09 | POST listings | 04, 09 |
| Empty | Sí | UI-10 | 200 [] | 16 |
| Error | Sí | UI-11 | 5xx/red | 16 |
| Loading | Sí | spec 16 (sobre UI-04) | pending | 16, 17 |
| Mobile wizard | Sí | spec 16 (variante UI-01) | — | 10, 16, 17 |
| Design system | Sí | UI-00 | — | 08 |
| Seed 5 PUBLISHED + 1 DRAFT | Sí | — | seed | 05 |
4. Cobertura Paper (literales)
| ID | Nombre | En 00-paper-reference |
|---|---|---|
| UX-00 | Cover | Sí |
| UX-01 | Stakeholders | Sí |
| UX-02 | Personas | Sí |
| UX-03 | JTBD | Sí |
| UX-04 | Stories | Sí |
| UX-05 | Journey | Sí |
| UX-06 | Blueprint | Sí |
| UX-07 | Site map | Sí |
| UX-08 | Flujos | Sí |
| UX-09 | Datos+permisos | Sí |
| UX-10 | Métricas | Sí |
| UX-11 | Research | Sí |
| UI-00 | Tokens | Sí |
| UI-01 | Wizard 1 zona | Sí |
| UI-02 | Wizard 2 presupuesto | Sí |
| UI-03 | Wizard 3 musts | Sí |
| UI-04 | Comparador 3 cols | Sí |
| UI-05 | Ficha | Sí |
| UI-06 | Login | Sí |
| UI-07 | Mesa | Sí |
| UI-08 | Alta | Sí |
| UI-09 | Success | Sí |
| UI-10 | Empty | Sí |
| UI-11 | Error | Sí |
[COMPROBADO] UX-count: 12 y UI-count: 12 literales para el gate check-paper-reference.mjs. Paper hi-fi en construcción paralela; inventario planificado escrito.
5. Rúbrica de calidad (auto SCORE orientativo · docs)
| Eje | Score 1–5 | Comentario |
|---|---|---|
| Diversidad | 5 | Wizard+comparador vs galería MARGEN, tabla CESTA y parte CANTERA; L1 no L2 |
| Craft visual (spec) | 4–5 | Rusted + Instrument/Sora + fotos de finca documentadas |
| Densidad UX docs | 5 | Suite L1: tablas, AC, etiquetas S/H/D |
| Completitud código L1 | — | App fuera o en paralelo; leftover scaffold anotado |
| Authz | 5 | Contrato: guard en mesa/POST/PATCH; DRAFT 404 |
| Verdad investigación | 5 | Sin entrevistas ni stats de campo falsas |
6. Huecos aceptados (no regresiones de cierre documental)
| Hueco | Clasificación |
|---|---|
| Visita / lead / mapa / search-home | Fuera L1 |
| e2e automatizado | L+1 |
| Preview token de DRAFT | L+1 |
| Comparison persistida siempre | Opcional; contrato de response cierra el Must |
Leftover issues / pieces / events en repo | Build; contrato de esta suite gana |
| Paper/app no necesariamente tocados aquí | Encargo = documentación |
7. Veredicto
COMPLETO para entrega documental del caso 2026-08-18.
La suite define producto L1 usable (tablas, AC, riesgos, contratos) sin presentar investigación de campo como hecho.
Cualquier ampliación visita / lead / mapa / search-first / lookbook requiere nuevo brief de diversidad, no parche silencioso.
19-backlog-completo.md
19 — Backlog completo — ZAGUÁ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-18)
| ID | Ítem | Capa |
|---|---|---|
| D01 | Definición producto ZAGUÁN + eslogan | Docs |
| D02 | Day brief diversidad inmobiliaria L1 | Docs (preexistente, no pisado) |
| D03 | Terna R-IN · S-WIZ · H-WIZARD · F-CMP | Docs |
| D04 | Personas Marcos Vidal / Lucía Ferrer / Inés Roldán | Docs + Paper ref |
| D05 | JTBD + stories Must | Docs |
| D06 | IA wizard-first + chrome S-WIZ | Docs |
| D07 | Flujos F1–F11 + AC | Docs |
| D08 | Modelo users / listings / comparisons | Docs |
| D09 | Contratos API :3018 | Docs |
| D10 | Auth JWT AGENT | Docs |
| D11 | Estados DRAFT / PUBLISHED + 404 de borrador | Docs |
| D12 | Comparador 3 cols (precio, m², planta, gastos, extras) | Docs |
| D13 | Microcopy ×4 (reservar / oferta / visitado / no llama) | Docs |
| D14 | North star compare + ficha | Docs |
| D15 | Tokens rusted + Instrument Serif / Sora | Docs |
| D16 | Paper UX-00…11 + UI-00…11 | Docs |
| D17 | Suite docs 00–20 + README + executive | Docs |
| D18 | Seed 5 PUBLISHED + 1 DRAFT | Docs |
| D19 | Assets listados (hero + 5 fincas) | README |
| D20 | QA smoke + S1–S25 | Docs |
| D21 | Neon curly-recipe-23023272 | Docs |
| D22 | Wizard 3 pasos especificado | Docs |
| D23 | Ground G-CONCRETE #E3E1DC | Docs |
| D24 | Rutas reales solo las 9 del encargo | Docs |
Los must-have del brief (wizard, comparador, ficha, login+mesa, alta+success, empty/loading/error/mobile, ≥12 UX + ≥10 UI) están dentro de D01–D24, no como leftover.
2. Backlog L1 implementación (repo zaguan-app)
Alineación de código al contrato ya cerrado. No es alcance documental pendiente.
| ID | Ítem | Prioridad | Notas |
|---|---|---|---|
| I01 | Alinear apps/api + apps/web al contrato ZAGUÁN | P0 | quitar leftover issues/pieces/events |
| I02 | Schema listings / users / comparisons? | P0 | |
| I03 | Seed Lucía + 5 PUBLISHED + 1 DRAFT | P0 | |
| I04 | GET listings / listings/:slug / POST compare | P0 | DRAFT → 404 |
| I05 | POST listings + PATCH :id { status } | P0 | |
| I06 | POST login JWT | P0 | lucia@zaguan.barrio |
| I07 | GET mesa/listings | P0 | |
| I08 | Wizard 3 pasos Angular | P0 | S-WIZ |
| I09 | Comparar / ficha / login / mesa / alta / ok | P0 | |
| I10 | Tokens Tailwind + fotos | P0 | |
| I11 | Smoke curl + build | P0 | D-P1-06 |
| I12 | Puerto API 3018 | P0 |
3. Backlog L+1 (polish)
| ID | Ítem | Prioridad | Notas |
|---|---|---|---|
| B01 | canActivate guards Angular | P1 | UX auth + wizard |
| B02 | Persistencia Comparison + replay | P2 | home sigue paso 1 |
| B03 | Preview token DRAFT | P1 | no 200 público |
| B04 | Rate limit login | P1 | anti stuffing |
| B05 | Playwright smoke | P1 | CI |
| B06 | Skeleton UI-04 en código | P2 | |
| B07 | Mismatch badges si se relajan musts | P3 | |
| B08 | Confirm al revertir a DRAFT | P2 | |
| B09 | API URL por environment | P1 | deploy |
| B10 | Página 404 amigable | P3 | |
| B11 | Sessions invalidables | P2 | |
| B12 | Foto merced-2 al publicar | P2 |
4. Backlog L3 (requiere brief nuevo)
| ID | Ítem | Dependencia |
|---|---|---|
| C01 | Multi-agencia / varios locales | venues + membership |
| C02 | Cuenta BUSCADOR + favoritos | authz |
| C03 | Calendario de visitas | H2 se rompe · brief nuevo |
| C04 | Lead “te llamamos” | S2 se rompe |
| C05 | Pasarela / señal | S4 se rompe |
| C06 | Mapa como home | rompe terna |
| C07 | Search-first home | rompe terna |
| C08 | CRM 40 estados | S3 se rompe |
5. Backlog explícitamente fuera
| ID | Ítem |
|---|---|
| E01 | Reserva / slot / confirmación de visita |
| E02 | Lookbook editorial / agenda semanal |
| E03 | Tabla de necesidades / compromiso de caja |
| E04 | Parte de obra / wizard de oficio |
| E05 | Feed + dock |
| E06 | App nativa |
| E07 | Hero 2-col + 3 cards de marca |
6. Explicitamente no-backlog
| Idea | Razón |
|---|---|
| Hero 2-col + 3 cards | AP-12 / rompe H-WIZARD |
| Masthead de periódico | S-TOP de MARGEN |
| Sidebar CRM | S-SIDE |
| Dock flotante | S-DOCK / S-BOTTOM |
| Rail de iconos | S-RAIL de CESTA |
| Command search municipal | PIZARRA |
| Form “pide visita” | H2 |
| “Te llamamos” | S2 / H3 |
7. Orden de ataque recomendado (continuidad de código)
- I01–I12 (vertical slice runnable, sin leftover)
- B01 + B05 + B09
- B03 + B08
- B02 + B07 si el empty es frecuente
- Evaluar brief L3 — no parche silencioso
8. Trazabilidad
| Origen | Ítems |
|---|---|
| Day brief must-have | D01–D24, I01–I12 |
| Doc 15 L+1 | B01–B12 |
| Doc 15 L3 / fuera | C01–C08, E01–E07 |
20-implementation.md
20 — Implementación — ZAGUÁN
1. Resumen ejecutivo técnico
| Campo | Valor |
|---|---|
| App path | /Users/cristian/orca/zaguan-app |
| API | NestJS · puerto 3018 · prefijo /api |
| Web | Angular standalone · puerto 4200 |
| Package | apps independientes (npm --prefix o pnpm --filter) |
| DB | Neon PostgreSQL · project curly-recipe-23023272 |
| Tablas | users, listings (+ sessions Should, comparisons opcional) |
| Auth | JWT Bearer · role AGENT |
| Dominio | User, Listing, Comparison (opcional) |
| Fecha | 2026-08-18 |
| GitHub | https://github.com/Criscode2022/zaguan-app |
Este documento es la especificación de build alineada al case. No sustituye al código: si el repo diverge, gana el contrato de docs 05 + este archivo tras actualizar ambos.
[COMPROBADO] Path, puerto, Neon, Paper y GitHub salen del encargo.
[COMPROBADO] El repo existe; contiene leftover de MARGEN (issues, pieces, events, assets de semanario). El contrato de esta suite es ZAGUÁN.
2. Cómo arrancar
cd /Users/cristian/orca/zaguan-app
# instalar dependencias por app (no npm workspaces)
# apps/api/.env
# DATABASE_URL= # Neon curly-recipe-23023272
# JWT_SECRET=
# PORT=3018
# schema + seed
# api → http://localhost:3018
# web → http://localhost:4200
Credenciales
| Rol | Password | |
|---|---|---|
| AGENTE | lucia@zaguan.barrio | password123 |
3. Schema
Al boot o vía migrate:
CREATE TABLE/ migrate deusers,listings(doc 05). Opcionalsessions,comparisons.- Índices:
listings(slug),listings(status, zona, rent),listings(updated_at DESC),users(email). - Seed idempotente:
- upsert AGENTE
lucia@zaguan.barrio(bcrypt depassword123) - upsert
listingsporslug
- upsert AGENTE
No borrar datos de agente en cada boot si ya existen filas distintas al seed.
Tablas (recordatorio)
| Tabla | Contenido |
|---|---|
users | AGENTE Lucía |
listings | 5 PUBLISHED + 1 DRAFT |
comparisons | Opcional: criterio + slugs |
sessions | Should: sid + token_hash + expires_at |
4. Módulos API a implementar
Auth
POST /api/auth/login- Valida email/password; compara bcrypt; emite JWT con
sub, email, role. - Guard JWT +
role === AGENTprotege mesa / escritura.
Listings / compare
| Método | Ruta | Auth | Notas |
|---|---|---|---|
| GET | /api/listings | No | PUBLISHED; query zona, maxRent, rooms, elevator, terrace, pets, renovation |
| POST | /api/compare | No | body criterio → { criteria, listings[≤3] } |
| GET | /api/listings/:slug | No | 404 si no existe o DRAFT |
| GET | /api/mesa/listings | JWT | todas; updatedAt desc |
| POST | /api/listings | JWT | crea DRAFT; slug kebab |
| PATCH | /api/listings/:id | JWT | { status } y/o campos |
Slug
kebab(address o title) + opcional "-2" si unique falla
Reintentar sufijo (máx. 5). Seed usa slugs fijos (amparo-14, …).
[DECISIÓN] Timezone Europe/Madrid para updatedAt de UI. JSON transporta ISO UTC. Rentas y gastos son enteros en euros.
Filtro público
GET /listings = WHERE status = 'PUBLISHED' [AND query]
GET /listings/:slug = 404 unless status = 'PUBLISHED'
POST /compare = top 3 PUBLISHED por score (doc 04)
Publicar no toca visitas ni genera llamada.
Score de compare (referencia de implementación)
hard: tipo AND rent <= maxRent AND (must ON ⇒ field = true)
sort: zona match DESC, rooms exact DESC, (maxRent - rent) ASC
limit 3
5. Frontend a implementar
| Página | Ruta | Responsabilidad |
|---|---|---|
| ZonaPage | / | paso 1, chips, persist criterio, chrome S-WIZ |
| PresupuestoPage | /criterio/presupuesto | tope + hab + disclaimer oferta |
| MustsPage | /criterio/musts | toggles + POST compare |
| CompararPage | /comparar | 3 cols, empty/loading/error |
| FincaPage | /fincas/:slug | GET slug, ficha, 404 DRAFT |
| LoginPage | /login | form → login → mesa |
| DeskPage | /mesa | list + PATCH en fila + empty/error + logout |
| NewListingPage | /mesa/nueva | form create + Guardar / Publicar |
| OkPage | /mesa/ok/:slug | confirmación + “publicado ≠ visitado” |
Shell pública: WizardChromeComponent. Mesa: wordmark + Salir, sin pasos.
ApiService centraliza base URL http://localhost:3018/api, token storage (zaguan_token, zaguan_user), criterio (zaguan_criteria), métodos tipados (Listing, CompareResponse, User).
6. Decisiones de implementación
| Decisión | Razón |
|---|---|
| Puerto API 3018 | Evitar colisión con MARGEN 3017 |
Tablas listings / users | Dominio inmobiliario, no leftover pieces / issues |
Role AGENT | Encargo; no EDITOR |
| Soft auth en páginas de mesa | Simple; API es autoridad |
| Soft guard de wizard | Sin zona no hay paso 2 |
| Sin FK User–Listing | Agencia de 1 |
| Slug kebab | URL de lectura humana |
| Templates standalone | Velocidad daily |
| Crear siempre DRAFT | Borrador invisible |
| Home no usa hero 2-col | AP-12 / H-WIZARD |
| PUBLISHED no VISITED / RESERVED | Dominio de catálogo, no CRM |
| JSON camelCase | Convenio web serie daily |
PATCH :id con { status } | Contrato del encargo |
Success /mesa/ok/:slug | Board UI-09 |
| Cover allowlist | Evitar URL arbitraria |
| Comparison opcional | Must = response, no tabla |
7. Variables de entorno
| Variable | Servicio | Descripción |
|---|---|---|
DATABASE_URL | API | Neon curly-recipe-23023272 |
JWT_SECRET | API | Firma tokens |
PORT | API | opcional, 3018 |
JWT_EXPIRES | API | opcional, default 12h |
Web: URL de API en ApiService (default localhost:3018).
8. Smoke de implementación (mínimo)
- Schema OK (tablas existen).
GET /api/listingsincluye 5 slugs PUBLISHED y cero DRAFT.GET /api/listings/merced-2404.POST /api/compareUSERA / ALQUILER / 950 / 2 devuelve amparo-14 y yeserias-8.POST /api/auth/login200 +accessToken.GET /api/mesa/listingsBearer incluye el DRAFT.GET /api/mesa/listingssin token 401.PATCH /api/listings/:id{ status: "PUBLISHED" }200 y la finca aparece en listings.- Web: wizard dominante, 3 columnas, disclaimers ×4, fotos 4:3.
9. Estructura de ficheros clave (objetivo)
zaguan-app/
├── package.json
├── apps/api/
│ ├── src/
│ │ ├── main.ts
│ │ ├── app.module.ts
│ │ ├── auth/
│ │ ├── listings/
│ │ └── compare/
│ └── .env.example
├── apps/web/
│ ├── tailwind.config.js
│ └── src/app/
│ ├── app.routes.ts
│ ├── core/api.service.ts
│ ├── shell/wizard-chrome.component.ts
│ └── pages/
│ ├── zona/
│ ├── presupuesto/
│ ├── musts/
│ ├── comparar/
│ ├── finca/
│ ├── login/
│ ├── desk/
│ ├── new-listing/
│ └── ok/
└── README.md
Nombres de fichero orientativos; el contrato es de rutas y tablas, no de filenames exactos.
10. Seed — checklist de implementación
| # | Check |
|---|---|
| 1 | User Lucía Ferrer / lucia@zaguan.barrio / password bcrypt de password123 |
| 2 | 5 listings PUBLISHED con slugs y covers de doc 05 |
| 3 | 1 listing DRAFT merced-2 sin cover público |
| 4 | Zonas USERA / VALLECAS / ENTREVIAS / ORCASUR |
| 5 | Tipo ALQUILER en las 6 |
| 6 | Musts y extras alineados (patio, terraza, mascotas, reforma, ascensor) |
| 7 | Idempotencia: upsert por email / slug |
11. Alineación case ↔ app
| Doc | Evidencia esperada en código |
|---|---|
| 05 data model | columnas y enums |
| 03–04 IA/flows | routes + controllers |
| 08 DS | tailwind colors + fontFamily Instrument/Sora |
| 09 content | strings en templates (microcopy ×4) |
| 00 paper | URLs en README case |
| 11 security | Guard en mesa/POST/PATCH; DRAFT 404 |
12. Notas de cierre técnico
- El case documental L1 está especificado (docs + Paper ref + contratos).
- Must-have de producto del brief están escritos con AC.
- Mejoras (preview token, comparison persistida, e2e, rate limit) viven en backlog L+1, no como deuda silenciosa.
- Si el repo aún muestra
issues/pieces/events/ fotos de merienda, no está alineado: I01 es el primer ítem de build.