Files
localesp/src/components/PasoResumen.jsx
T
edgar.friendly 003b1e209f
deploy-branch / deploy (push) Failing after 6s
deploy-branch / teardown (push) Skipped
feat(propuesta): pasarela paso a paso con autodetección de ubicación y FAB
- Campo universal de ubicación (Google Maps/Waze/OSM/coordenadas/dirección)
  sobre proveedores extensibles (src/utils/ubicacion) con resolver e inferencia
  inversa Nominatim (nombre/provincia pre-rellenados, alias cooficiales)
- FAB flotante abajo a la derecha que abre la pasarela en un modal accesible
  (Escape, foco contenido, responsive); sin descripción ni puntuación;
  avisos de duplicado y filtro de palabras dentro del modal
- Aviso PWA reposicionado: tarjeta abajo a la izquierda en móvil,
  botón compacto arriba a la izquierda en PC (no tapa el FAB)
- Suite de pruebas: 88 unitarias/componente (vitest + RTL, offline) y
  10 de integración (supertest + SQLite :memory: y Nominatim real opt-in)
- CI: workflow de Gitea Actions con npm test + build en push/PR e
  integración manual
2026-08-16 01:35:02 +02:00

120 lines
6.2 KiB
React

import { CATEGORIAS } from "../constants/categorias.js";
import { ETIQUETAS_PROVEEDOR } from "../utils/ubicacion/index.js";
const fila = { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10, padding: "6px 0", borderBottom: "0.5px solid var(--color-border-tertiary)", fontSize: 13 };
const clave = { color: "var(--color-text-secondary)", flexShrink: 0 };
const valor = { textAlign: "right", fontWeight: 500, wordBreak: "break-word" };
// Paso 6 (final) de la pasarela: resumen legible de todo lo introducido,
// aceptación obligatoria de la política de privacidad y envío de la
// propuesta. Los avisos de palabra prohibida y de posible duplicado se
// muestran dentro del propio modal.
export default function PasoResumen({
form, aceptaPrivacidad, setAceptaPrivacidad, onAbrirPrivacidad,
onEnviar, onConfirmarDuplicado, onDescartarDuplicado,
errorFiltro, posibleDuplicado, comprobandoDuplicado, saving,
}) {
const categoria = form.categoria ? CATEGORIAS[form.categoria] : null;
const enviando = comprobandoDuplicado || saving;
const etiquetaProveedor = form.proveedorUbicacion ? ETIQUETAS_PROVEEDOR[form.proveedorUbicacion] || form.proveedorUbicacion : "";
return (
<div>
<p style={{ margin: "0 0 8px", fontSize: 13, color: "var(--color-text-secondary)" }}>
Comprueba los datos antes de enviar. El administrador revisará la propuesta antes de publicarla.
</p>
<div style={{ background: "var(--color-background-secondary)", borderRadius: "var(--border-radius-md)", padding: "4px 12px", marginBottom: 12 }}>
<div style={fila}>
<span style={clave}>📍 Ubicación</span>
<span style={valor}>
{etiquetaProveedor && <>{etiquetaProveedor}<br /></>}
{form.lat != null && form.lng != null && <span style={{ fontWeight: 400 }}>{form.lat.toFixed(4)}, {form.lng.toFixed(4)}</span>}
{form.direccion && <span style={{ fontWeight: 400, display: "block" }}>{form.direccion}</span>}
</span>
</div>
<div style={fila}>
<span style={clave}>🏷️ Nombre</span>
<span style={valor}>{form.nombre}</span>
</div>
<div style={fila}>
<span style={clave}>🗺️ Provincia</span>
<span style={valor}>{form.provincia}</span>
</div>
<div style={fila}>
<span style={clave}>📂 Categoría</span>
<span style={valor}>{categoria ? `${categoria.emoji} ${form.categoria}` : ""}</span>
</div>
<div style={{ ...fila, borderBottom: "none" }}>
<span style={clave}>🔖 Tipo específico</span>
<span style={{ ...valor, fontWeight: form.subcategoria ? 500 : 400, color: form.subcategoria ? "inherit" : "var(--color-text-tertiary)" }}>
{form.subcategoria || "—"}
</span>
</div>
</div>
<label style={{ display: "flex", alignItems: "flex-start", gap: 8, marginBottom: 12, fontSize: 12, color: "var(--color-text-secondary)", cursor: "pointer" }}>
<input
type="checkbox"
checked={aceptaPrivacidad}
onChange={(e) => setAceptaPrivacidad(e.target.checked)}
style={{ marginTop: 2 }}
/>
<span>
He leído y acepto la{" "}
<button
type="button"
onClick={(e) => { e.preventDefault(); onAbrirPrivacidad(); }}
style={{ background: "none", border: "none", padding: 0, color: "#8B0000", textDecoration: "underline", cursor: "pointer", fontSize: 12 }}
>
política de privacidad
</button>
{" "}sobre cómo se usan estos datos. <span style={{ color: "#8B0000" }}>*</span>
</span>
</label>
{/* Aviso de palabra prohibida */}
{errorFiltro && (
<div role="alert" style={{ marginBottom: 12, background: "#FDEDEC", border: "1px solid #F5B7B1", borderRadius: "var(--border-radius-md)", padding: "10px 14px", fontSize: 13, color: "#922B21", display: "flex", alignItems: "center", gap: 8 }}>
<i className="ti ti-ban" aria-hidden="true"></i> {errorFiltro}
</div>
)}
{/* Aviso de posible duplicado */}
{posibleDuplicado && (
<div role="alert" style={{ marginBottom: 12, background: "#FDF2E9", border: "1px solid #F5CBA7", borderRadius: "var(--border-radius-md)", padding: "10px 14px", fontSize: 13, color: "#784212" }}>
<div style={{ display: "flex", alignItems: "center", gap: 8, fontWeight: 500 }}>
<i className="ti ti-alert-triangle" aria-hidden="true"></i> Ya existe un local parecido
</div>
<ul style={{ margin: "6px 0", paddingLeft: 20 }}>
{posibleDuplicado.coincidencias.map((c) => (
<li key={c.id}>{c.nombre} · {c.provincia}{c.direccion ? ` · ${c.direccion}` : ""} ({c.estado === "pendiente" ? "pendiente de revisión" : "ya publicado"})</li>
))}
</ul>
<div style={{ display: "flex", gap: 8, marginTop: 4 }}>
<button onClick={onConfirmarDuplicado} disabled={saving}
style={{ background: "#8B0000", color: "white", border: "none", borderRadius: "var(--border-radius-md)", padding: "6px 14px", fontSize: 12, fontWeight: 500, cursor: "pointer" }}>
{saving ? "Enviando..." : "Enviar de todos modos"}
</button>
<button onClick={onDescartarDuplicado} style={{ background: "none", border: "0.5px solid var(--color-border-secondary)", borderRadius: "var(--border-radius-md)", padding: "6px 14px", fontSize: 12, cursor: "pointer", color: "var(--color-text-secondary)" }}>Revisar datos</button>
</div>
</div>
)}
<button
onClick={onEnviar}
disabled={!aceptaPrivacidad || enviando}
style={{
background: aceptaPrivacidad ? "#8B0000" : "var(--color-background-secondary)",
color: aceptaPrivacidad ? "white" : "var(--color-text-tertiary)",
border: "none", borderRadius: "var(--border-radius-md)",
padding: "10px 20px", fontSize: 14, fontWeight: 500, width: "100%",
cursor: aceptaPrivacidad && !enviando ? "pointer" : "not-allowed",
}}
>
{comprobandoDuplicado ? "Comprobando..." : saving ? "Enviando..." : "📨 Enviar propuesta"}
</button>
</div>
);
}