Panel de filtros
Implementa: web, app
Consume: api
Estado: activa
Última revisión: 2026-05-21T19:38:35Z
# Implementa: web, app
# Consume: api
# Estado: activa
# Última revisión: 2026-05-21T19:38:35Z
# Position: 4
Feature: Panel de filtros
El panel de filtros permite al usuario configurar su búsqueda de gasolineras.
Es un bottom sheet accesible desde el botón de filtros en la barra superior.
Los filtros no generan llamadas en tiempo real dentro del panel. La llamada
a /cheapest se realiza únicamente cuando el usuario confirma pulsando el CTA.
Si el usuario cierra el panel (X o toque fuera) sin pulsar el CTA, no se
realiza ninguna llamada al API.
La configuración del panel (/filters) se carga la primera vez que el usuario
abre el panel y se cachea en memoria para aperturas posteriores. Antes de esa
primera apertura, la pantalla principal usa los valores del local storage o los
fallbacks hardcodeados: radio 10 km, combustible sp95 ("Gasolina 95").
Los defaults devueltos por /filters solo se aplican cuando el usuario pulsa
"Reiniciar" — no en la inicialización del panel.
El badge del botón de filtros cuenta solo filtros adicionales: marcas seleccionadas
y "Abierto ahora". Radio y combustible siempre están activos y se muestran en la
barra de subtítulo — no suman al badge.
# Contrato: GET /v2/filters
# Swagger: https://tanko-a4q2.onrender.com/tanko_docs_ad0df4baad113557
Background:
Given el usuario tiene geolocalización disponible
Scenario: Abrir el panel
Given el usuario está en la pantalla principal
When pulsa el botón de filtros
Then se abre el panel mostrando la configuración confirmada actual
Scenario: El botón de filtros está siempre habilitado con geolocalización
Given el usuario tiene geolocalización disponible
And el endpoint /filters no ha sido llamado aún
Then el botón de filtros está habilitado
Scenario: El panel no se abre sin geolocalización
Given el usuario no ha concedido permisos de ubicación
Then el botón de filtros aparece desactivado
And el panel no se puede abrir
Scenario: Primera apertura del panel llama a /filters
Given el usuario nunca ha abierto el panel de filtros
When pulsa el botón de filtros
Then el frontend llama a /filters para cargar la configuración del panel
Scenario: Panel en primera apertura sin persistencia usa fallbacks hardcodeados
Given el usuario nunca ha persistido filtros
When abre el panel por primera vez
Then el slider muestra 10 km
And el combustible activo es "Gasolina 95"
And los defaults devueltos por /filters no se aplican como valores iniciales
Scenario: Panel en primera apertura con persistencia usa local storage
Given el usuario tiene guardado radio 20 km y "Diésel"
When abre el panel por primera vez
Then el slider muestra 20 km
And el combustible activo es "Diésel"
Scenario: Si /filters falla en la primera apertura sin persistencia
Given el endpoint /filters no responde
And el usuario no tiene filtros persistidos
When pulsa el botón de filtros
Then el panel se abre mostrando los fallbacks hardcodeados (10 km, sp95)
And el slider aparece desactivado (sin min/max/step del API)
And el CTA no es accionable
Scenario: El panel se abre con persistencia aunque /filters falle
Given el endpoint /filters no responde
And el usuario tiene filtros persistidos
When pulsa el botón de filtros
Then el panel se abre mostrando los valores persistidos
Scenario: Cerrar con X descarta los cambios
Given el panel está abierto con cambios no confirmados
When el usuario pulsa la X
Then el panel se cierra
And los filtros de la pantalla principal no cambian
Scenario: Cerrar pulsando fuera descarta los cambios
Given el panel está abierto con cambios no confirmados
When el usuario pulsa fuera del panel
Then el panel se cierra
And los filtros de la pantalla principal no cambian
Scenario: El CTA muestra texto fijo mientras el panel está abierto
Given el panel de filtros está abierto
Then el CTA muestra "Aplicar filtros"
And el texto no cambia al modificar radio, combustible, marcas o el toggle
Scenario: Pulsar el CTA lanza /cheapest y cierra el panel al resolver
Given el panel de filtros está abierto con cualquier combinación de filtros
When el usuario pulsa el CTA
Then el frontend llama a /cheapest con los filtros configurados en el borrador
And el CTA muestra un skeleton animado mientras la llamada está en vuelo
And los controles del panel no son accionables durante ese tiempo
When la llamada resuelve con éxito
Then el panel se cierra
And la pantalla principal se actualiza con los resultados y los filtros confirmados
Scenario: Si /cheapest falla al pulsar el CTA el panel permanece abierto
Given el usuario pulsa el CTA
And la llamada a /cheapest falla
Then el CTA vuelve a mostrar "Aplicar filtros" y es accionable de nuevo
And se muestra un mensaje de error en el panel
And los filtros de la pantalla principal no cambian
And el usuario puede reintentar o cerrar sin aplicar
Scenario: Los controles del panel están bloqueados mientras el CTA está en vuelo
Given el panel está abierto
When el usuario ha pulsado el CTA y la llamada a /cheapest está en vuelo
Then los chips de combustible no son accionables
And el toggle "Abierto ahora" no es accionable
And el slider no es accionable
# Evita que el usuario modifique filtros mientras la confirmación está en vuelo
Scenario: Reabrir el panel tras cerrar sin confirmar
Given el usuario cerró el panel sin confirmar cambios
When vuelve a abrir el panel
Then el panel muestra la configuración confirmada actual
And no hay llamada nueva a /filters
Scenario: Reiniciar todos los filtros
Given el panel está abierto con filtros modificados
When el usuario pulsa "Reiniciar"
Then radio y combustible vuelven a los defaults devueltos por el API
And la selección de marcas se limpia
And el toggle "Abierto ahora" vuelve al default devuelto por el API
And se borra la persistencia de todos los filtros
# Nota: "Reiniciar" es el único momento en que los defaults del API
# se aplican como valores activos. La inicialización usa local storage
# o los fallbacks hardcodeados (10 km, sp95).
Scenario: La barra de subtítulo muestra los fallbacks antes de abrir el panel
Given el usuario nunca ha abierto el panel de filtros
And no tiene filtros persistidos
Then la barra muestra "CERCA DE TI · [CIUDAD] · 10 KM · GASOLINA 95"
Scenario: La barra de subtítulo refleja la persistencia antes de abrir el panel
Given el usuario tiene guardado radio 25 km y "Diésel"
And no ha abierto el panel todavía
Then la barra muestra "CERCA DE TI · [CIUDAD] · 25 KM · DIÉSEL"
Scenario: La barra de subtítulo refleja los filtros confirmados
Given el usuario ha confirmado radio 24 km y Gasolina 95
Then la barra muestra "CERCA DE TI · SEVILLA · 24 KM · GASOLINA 95"
Scenario: Badge en el botón de filtros con filtros adicionales activos
Given el usuario tiene marcas específicas seleccionadas y "Abierto ahora" activo
Then el botón de filtros muestra un badge con valor 2
And el badge usa el token de color `lime`
Scenario: Badge desaparece cuando los filtros adicionales están en default
Given el usuario no tiene marcas específicas ni "Abierto ahora" activo
Then el botón de filtros no muestra badge
# ---------------------------------------------------------------------------
# Layout de la bottom sheet en pantallas pequeñas
# ---------------------------------------------------------------------------
# Este patrón (header fijo + cuerpo scrollable + footer fijo) aplica a
# cualquier bottom sheet que tenga un CTA y contenido variable o largo.
Scenario: La sheet no supera la altura del viewport
Given el dispositivo tiene una pantalla pequeña (ej. iPhone SE)
And el contenido del panel excede la altura disponible
Then la sheet ocupa como máximo el 90% de la altura del viewport
And el 10% superior queda visible como espacio de cierre
Scenario: La sheet se ajusta al contenido cuando este es corto
Given el contenido del panel no supera el 90% del viewport
Then la altura de la sheet se ajusta al contenido
And no ocupa más espacio del necesario
Scenario: El header es fijo y siempre visible
Given el panel está abierto y el usuario hace scroll en el cuerpo
Then el título y las acciones "Reiniciar" y cerrar permanecen fijos en la parte superior
And no se desplazan con el scroll del contenido
Scenario: El cuerpo es scrollable cuando el contenido no cabe
Given el contenido del panel excede la altura disponible entre header y footer
Then el usuario puede hacer scroll dentro del cuerpo del panel
And el scrollbar no es visible
Scenario: El footer con el CTA está siempre anclado al fondo
Given el panel está abierto
Then el botón "Aplicar filtros" permanece fijo en la parte inferior del panel
And tiene el mismo padding horizontal que el resto del contenido
And es visible sin necesidad de hacer scroll