chore(openspec): archiva pasarela-proponer-local y sincroniza specs principales
- Mueve el cambio (36/36 tareas, 4/4 artefactos) a changes/archive/ - Crea las specs principales de boton-flotante-proponer-local, pasarela-proponer-local y proveedores-ubicacion (con Purpose)
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
||||
# Botón flotante para proponer locales
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Botón flotante fijo en la esquina inferior derecha
|
||||
El sistema SHALL mostrar un botón de acción flotante (FAB) fijado en la esquina inferior derecha de la ventana, siempre visible sobre el contenido, tanto en PC como en móvil.
|
||||
|
||||
#### Scenario: Visible en escritorio
|
||||
- **WHEN** la página se carga en una pantalla de escritorio
|
||||
- **THEN** el botón flotante aparece fijado en la esquina inferior derecha, por encima del resto del contenido
|
||||
|
||||
#### Scenario: Visible en móvil
|
||||
- **WHEN** la página se carga o se hace scroll en un dispositivo móvil
|
||||
- **THEN** el botón flotante permanece fijo y accesible en la esquina inferior derecha, sin quedar oculto por el contenido ni interferir con la barra inferior existente
|
||||
|
||||
#### Scenario: Reemplazo del botón de cabecera
|
||||
- **WHEN** la página se carga
|
||||
- **THEN** ya no existe el botón "Proponer local" en la cabecera y el único punto de entrada para proponer un local es el botón flotante
|
||||
|
||||
### Requirement: Tooltip explicativo al pasar el ratón
|
||||
El botón flotante SHALL mostrar un tooltip al pasar el ratón por encima (hover) y al recibir foco por teclado, explicando que sirve para añadir un nuevo local.
|
||||
|
||||
#### Scenario: Tooltip en hover
|
||||
- **WHEN** el usuario pasa el ratón por encima del botón flotante
|
||||
- **THEN** aparece un tooltip con un texto explicativo del tipo "Añade un nuevo local al directorio"
|
||||
|
||||
#### Scenario: Tooltip accesible sin ratón
|
||||
- **WHEN** el botón flotante recibe el foco por teclado o se consulta con un lector de pantalla
|
||||
- **THEN** el botón expone su propósito mediante `aria-label`, de modo que la función sea accesible sin depender del hover
|
||||
|
||||
### Requirement: El botón abre la pasarela en un modal
|
||||
El botón flotante SHALL abrir, al pulsarlo, la pasarela de propuesta de local en un modal por encima de la página.
|
||||
|
||||
#### Scenario: Apertura de la pasarela
|
||||
- **WHEN** el usuario pulsa el botón flotante
|
||||
- **THEN** se abre un modal con la pasarela de propuesta, comenzando por la primera pantalla (Ubicación)
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# Pasarela de propuesta de local
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Pasarela paso a paso en un modal
|
||||
El sistema SHALL presentar la propuesta de un local como una pasarela (wizard) dentro de un modal, con un solo campo por pantalla, navegación atrás/siguiente e indicador de progreso.
|
||||
|
||||
#### Scenario: Una pantalla por campo
|
||||
- **WHEN** la pasarela está abierta
|
||||
- **THEN** cada pantalla muestra únicamente el campo correspondiente al paso actual, con botones para avanzar y retroceder
|
||||
|
||||
#### Scenario: Navegación hacia atrás
|
||||
- **WHEN** el usuario pulsa "Atrás" en una pantalla intermedia
|
||||
- **THEN** vuelve a la pantalla anterior conservando los datos ya introducidos
|
||||
|
||||
#### Scenario: Avance bloqueado sin dato válido
|
||||
- **WHEN** el campo de la pantalla actual está vacío o es inválido
|
||||
- **THEN** el botón "Siguiente" está deshabilitado y la pantalla no avanza
|
||||
|
||||
#### Scenario: Cierre del modal
|
||||
- **WHEN** el usuario cierra el modal (botón ✕, botón Cancelar o tecla Escape)
|
||||
- **THEN** la pasarela se cierra y se descarta el borrador en curso
|
||||
|
||||
### Requirement: Orden de las pantallas empezando por Ubicación
|
||||
La pasarela SHALL presentar las pantallas en este orden: 1) Ubicación, 2) Nombre, 3) Provincia, 4) Categoría, 5) Tipo específico (opcional), 6) Revisión y envío.
|
||||
|
||||
#### Scenario: Primera pantalla
|
||||
- **WHEN** se abre la pasarela
|
||||
- **THEN** la primera pantalla que se muestra es la de Ubicación
|
||||
|
||||
#### Scenario: Tipo específico omitible
|
||||
- **WHEN** el usuario está en la pantalla de Tipo específico
|
||||
- **THEN** puede continuar sin seleccionar ninguna subcategoría
|
||||
|
||||
### Requirement: Inferencia de Nombre y Provincia desde la Ubicación
|
||||
El sistema SHALL pre-rellenar los campos Nombre y Provincia a partir de la ubicación introducida en la primera pantalla (geocodificación inversa), permitiendo al usuario corregirlos.
|
||||
|
||||
#### Scenario: Nombre y provincia pre-rellenados
|
||||
- **WHEN** el usuario introduce una ubicación válida en la primera pantalla y esta se resuelve con éxito
|
||||
- **THEN** al llegar a las pantallas de Nombre y Provincia ambos campos aparecen pre-rellenados con los valores inferidos
|
||||
|
||||
#### Scenario: Valores corregibles
|
||||
- **WHEN** los valores inferidos no son correctos
|
||||
- **THEN** el usuario puede editarlos libremente antes de enviar
|
||||
|
||||
#### Scenario: Degradación sin inferencia
|
||||
- **WHEN** la inferencia falla o devuelve campos vacíos
|
||||
- **THEN** las pantallas de Nombre y Provincia aparecen vacías y el usuario los rellena manualmente, sin bloquear la pasarela
|
||||
|
||||
### Requirement: Sin campo de comentario ni puntuación
|
||||
La pasarela SHALL NOT incluir el campo de comentario/descripción ni el campo de puntuación; la propuesta se envía sin esos datos.
|
||||
|
||||
#### Scenario: Envío sin comentario ni puntuación
|
||||
- **WHEN** el usuario completa la pasarela y envía
|
||||
- **THEN** la propuesta se registra con nombre, provincia, categoría, subcategoría, dirección/enlace y coordenadas, sin descripción y sin puntuación
|
||||
|
||||
### Requirement: Revisión y aceptación de privacidad antes de enviar
|
||||
La última pantalla de la pasarela SHALL mostrar un resumen de los datos introducidos y el checkbox de aceptación de la política de privacidad, siendo este último obligatorio para enviar.
|
||||
|
||||
#### Scenario: Envío bloqueado sin aceptar privacidad
|
||||
- **WHEN** el usuario llega a la pantalla final sin marcar la casilla de privacidad
|
||||
- **THEN** el botón de envío está deshabilitado hasta que la marque
|
||||
|
||||
#### Scenario: Resumen antes de enviar
|
||||
- **WHEN** el usuario llega a la pantalla final
|
||||
- **THEN** ve un resumen legible de todos los datos introducidos con posibilidad de volver atrás a corregirlos
|
||||
|
||||
### Requirement: Validaciones existentes integradas en la pasarela
|
||||
La pasarela SHALL conservar las validaciones actuales del formulario: filtro de palabras prohibidas y comprobación de duplicados, mostrando los avisos dentro del modal.
|
||||
|
||||
#### Scenario: Palabra prohibida detectada
|
||||
- **WHEN** el texto introducido contiene una palabra no permitida
|
||||
- **THEN** se muestra un aviso dentro del modal y la propuesta no se envía
|
||||
|
||||
#### Scenario: Posible duplicado detectado
|
||||
- **WHEN** al enviar se detecta un local igual o muy cercano
|
||||
- **THEN** se muestra el aviso de duplicado dentro del modal con las coincidencias y las opciones de enviar de todos modos o revisar los datos
|
||||
|
||||
#### Scenario: Envío correcto
|
||||
- **WHEN** la propuesta se envía con éxito
|
||||
- **THEN** el modal se cierra y aparece la confirmación de propuesta enviada en la página principal
|
||||
|
||||
### Requirement: Modal responsive accesible
|
||||
El modal de la pasarela SHALL ser usable en PC (diálogo centrado) y móvil (ocupando prácticamente toda la pantalla), con foco contenido y cierre mediante Escape.
|
||||
|
||||
#### Scenario: Uso en móvil
|
||||
- **WHEN** la pasarela se abre en una pantalla estrecha
|
||||
- **THEN** el modal ocupa el ancho completo y es operativo con teclado y scroll táctil
|
||||
|
||||
#### Scenario: Foco contenido en el modal
|
||||
- **WHEN** el modal está abierto
|
||||
- **THEN** el foco de teclado permanece dentro del modal y la tecla Escape lo cierra
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
# Proveedores de ubicación (integraciones extensible)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Interfaz común para proveedores de ubicación
|
||||
Cada integración con un backend de ubicación SHALL implementarse como una clase que cumpla una misma interfaz: identificarse (`id`), indicar si reconoce una entrada (`detecta(entrada)`) y extraer la información disponible (`extrae(entrada)`).
|
||||
|
||||
#### Scenario: Contrato uniforme
|
||||
- **WHEN** se consulta cualquier proveedor registrado
|
||||
- **THEN** expone los métodos `detecta(entrada)` y `extrae(entrada)` con la misma firma y semántica, de modo que el consumidor no necesita conocer la clase concreta
|
||||
|
||||
### Requirement: Resultado de extracción extensible
|
||||
El método `extrae(entrada)` SHALL devolver un objeto de resultado extensible que incluya como mínimo las coordenadas (`lat`, `lng`) y el identificador del proveedor que las extrajo, pudiendo incorporar campos adicionales en el futuro sin romper a los consumidores.
|
||||
|
||||
#### Scenario: Extracción mínima
|
||||
- **WHEN** un proveedor extrae una ubicación válida
|
||||
- **THEN** el resultado contiene `lat`, `lng` y el identificador del proveedor
|
||||
|
||||
#### Scenario: Campos adicionales sin ruptura
|
||||
- **WHEN** en el futuro un proveedor añade nuevos campos al resultado (horarios, teléfono, fotos…)
|
||||
- **THEN** los consumidores existentes siguen funcionando sin modificación, ignorando los campos que no conocen
|
||||
|
||||
### Requirement: Autodetección del proveedor a partir de la entrada
|
||||
El sistema SHALL autodetectar automáticamente el proveedor adecuado para una entrada dada, recorriendo los proveedores registrados y usando el primero que la reconozca.
|
||||
|
||||
#### Scenario: Enlace de Google Maps
|
||||
- **WHEN** el usuario pega un enlace de Google Maps (formatos `@lat,lng`, `!3d…!4d…` o `?q=lat,lng`)
|
||||
- **THEN** el sistema lo resuelve con el proveedor de Google Maps y obtiene las coordenadas
|
||||
|
||||
#### Scenario: Enlace de Waze
|
||||
- **WHEN** el usuario pega un enlace de Waze (`waze.com/ul`, parámetro `ll=lat,lng` o búsqueda `q=`)
|
||||
- **THEN** el sistema lo resuelve con el proveedor de Waze y obtiene las coordenadas
|
||||
|
||||
#### Scenario: Enlace de OpenStreetMap
|
||||
- **WHEN** el usuario pega un enlace de openstreetmap.org (parámetros `mlat`/`mlon` o fragmento `#map=zoom/lat/lng`)
|
||||
- **THEN** el sistema lo resuelve con el proveedor de OpenStreetMap y obtiene las coordenadas
|
||||
|
||||
#### Scenario: Coordenadas geográficas sueltas
|
||||
- **WHEN** el usuario escribe coordenadas directamente (por ejemplo `41.3851, 2.1734`)
|
||||
- **THEN** el sistema lo resuelve con el proveedor de coordenadas y obtiene lat/lng
|
||||
|
||||
#### Scenario: Texto de dirección como fallback
|
||||
- **WHEN** la entrada no coincide con ningún enlace soportado y parece una dirección en texto
|
||||
- **THEN** el sistema la resuelve con el proveedor de geocodificación (Nominatim) realizando una búsqueda por texto
|
||||
|
||||
#### Scenario: Entrada no reconocida
|
||||
- **WHEN** la entrada no es reconocida por ningún proveedor ni resuelta como dirección
|
||||
- **THEN** el sistema informa de que no se pudo interpretar la ubicación y no avanza de pantalla
|
||||
|
||||
### Requirement: Inferencia de nombre y provincia
|
||||
Los proveedores SHALL extraer, cuando estén disponibles, los campos inferibles `nombre` y `provincia` a partir de la entrada resuelta (geocodificación inversa cuando solo hay coordenadas), normalizando la provincia al listado canónico de provincias de España.
|
||||
|
||||
#### Scenario: Inferencia desde coordenadas
|
||||
- **WHEN** la entrada se resuelve a coordenadas sin más contexto
|
||||
- **THEN** una geocodificación inversa obtiene un nombre descriptivo y la provincia canónica (coincidencia insensible a acentos y mayúsculas con el listado de PROVINCIAS)
|
||||
|
||||
#### Scenario: Provincia fuera del listado
|
||||
- **WHEN** la provincia inferida no coincide con ninguna provincia del listado canónico
|
||||
- **THEN** el campo provincia queda vacío para que el usuario lo seleccione manualmente
|
||||
|
||||
### Requirement: Extensibilidad sin modificar proveedores existentes
|
||||
Añadir un proveedor nuevo SHALL consistir en crear una nueva clase que implemente la interfaz y registrarla en el resolver, sin modificar las clases existentes ni la lógica de la pasarela (principio abierto/cerrado).
|
||||
|
||||
#### Scenario: Registro de un proveedor nuevo
|
||||
- **WHEN** se añade una clase `MiProveedor` con `detecta`/`extrae` y se registra en la lista de proveedores
|
||||
- **THEN** el campo universal empieza a aceptar las entradas que ese proveedor reconoce sin ningún otro cambio de código
|
||||
|
||||
### Requirement: Errores de red degradados
|
||||
Los proveedores SHALL tratar sus errores de red o de servicio devolviendo un fallo controlado, sin romper la pasarela ni propagar excepciones al consumidor.
|
||||
|
||||
#### Scenario: Fallo del servicio de geocodificación
|
||||
- **WHEN** Nominatim no responde o devuelve un error
|
||||
- **THEN** la pantalla de Ubicación muestra un aviso de error y el usuario puede reintentar o continuar sin inferencia
|
||||
Reference in New Issue
Block a user