Prototipo YB01 #1

Open
edgar.friendly wants to merge 16 commits from prototipo-yb01 into master
5 changed files with 57 additions and 19 deletions
Showing only changes of commit a3fd928073 - Show all commits
+8
View File
@@ -7,6 +7,14 @@
<meta name="description" content="Directorio colaborativo de negocios por toda España" />
<!-- No indexar el admin -->
<meta name="robots" content="index, follow" />
<!-- PWA -->
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#8B0000" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="LocalESP" />
<!-- Tabler Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3.11.0/dist/tabler-icons.min.css" />
<style>
+2 -1
View File
@@ -2,7 +2,8 @@
command = "npm run build"
publish = "dist"
# SPA: todas las rutas van al index.html (React gestiona /admin y /movil)
# SPA: todas las rutas van al index.html (React gestiona /admin; el resto es
# la PWA principal, instalable en móvil)
[[redirects]]
from = "/*"
to = "/index.html"
+29 -8
View File
@@ -11,10 +11,11 @@ db.exec(`
id TEXT PRIMARY KEY,
nombre TEXT NOT NULL,
descripcion TEXT,
ubicacion TEXT,
telefono TEXT,
email TEXT,
web TEXT,
provincia TEXT,
subcategoria TEXT,
direccion TEXT,
enlaceGoogleMaps TEXT,
puntuacion INTEGER DEFAULT 0,
categoria TEXT,
fecha TEXT DEFAULT CURRENT_TIMESTAMP,
lat REAL,
@@ -25,10 +26,11 @@ db.exec(`
id TEXT PRIMARY KEY,
nombre TEXT NOT NULL,
descripcion TEXT,
ubicacion TEXT,
telefono TEXT,
email TEXT,
web TEXT,
provincia TEXT,
subcategoria TEXT,
direccion TEXT,
enlaceGoogleMaps TEXT,
puntuacion INTEGER DEFAULT 0,
categoria TEXT,
estado TEXT DEFAULT 'pendiente',
fechaPropuesta TEXT DEFAULT CURRENT_TIMESTAMP,
@@ -49,4 +51,23 @@ db.exec(`
);
`);
// Migración suave: si la base de datos ya existía con el esquema antiguo
// (sin provincia/subcategoria/direccion/enlaceGoogleMaps/puntuacion),
// añadimos las columnas que falten sin borrar nada, porque
// CREATE TABLE IF NOT EXISTS no modifica tablas ya creadas.
function ensureColumn(table, column, definition) {
const cols = db.prepare(`PRAGMA table_info(${table})`).all().map(c => c.name);
if (!cols.includes(column)) {
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
}
}
for (const table of ["locales", "pendientes"]) {
ensureColumn(table, "provincia", "TEXT");
ensureColumn(table, "subcategoria", "TEXT");
ensureColumn(table, "direccion", "TEXT");
ensureColumn(table, "enlaceGoogleMaps", "TEXT");
ensureColumn(table, "puntuacion", "INTEGER DEFAULT 0");
}
export default db;
+8 -8
View File
@@ -24,16 +24,16 @@ app.get("/api/pendientes", (_req, res) => {
// Enviar propuesta
app.post("/api/propuestas", (req, res) => {
const { nombre, descripcion, ubicacion, telefono, email, web, categoria, lat, lng } = req.body;
const { nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion, categoria, lat, lng } = req.body;
const id = randomUUID();
const fechaPropuesta = new Date().toISOString();
db.prepare(`
INSERT INTO pendientes (id, nombre, descripcion, ubicacion, telefono, email, web, categoria, estado, fechaPropuesta, lat, lng)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pendiente', ?, ?, ?)
`).run(id, nombre, descripcion, ubicacion, telefono, email, web, categoria, fechaPropuesta, lat, lng);
INSERT INTO pendientes (id, nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion, categoria, estado, fechaPropuesta, lat, lng)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pendiente', ?, ?, ?)
`).run(id, nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion ?? 0, categoria, fechaPropuesta, lat, lng);
res.status(201).json({ id, nombre, descripcion, ubicacion, telefono, email, web, categoria, estado: "pendiente", fechaPropuesta, lat, lng });
res.status(201).json({ id, nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion: puntuacion ?? 0, categoria, estado: "pendiente", fechaPropuesta, lat, lng });
});
// Aprobar propuesta
@@ -47,9 +47,9 @@ app.post("/api/aprobar/:id", (req, res) => {
const fecha = new Date().toISOString();
db.prepare(`
INSERT INTO locales (id, nombre, descripcion, ubicacion, telefono, email, web, categoria, fecha, lat, lng)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(localeId, propuesta.nombre, propuesta.descripcion, propuesta.ubicacion, propuesta.telefono, propuesta.email, propuesta.web, propuesta.categoria, fecha, propuesta.lat, propuesta.lng);
INSERT INTO locales (id, nombre, descripcion, provincia, subcategoria, direccion, enlaceGoogleMaps, puntuacion, categoria, fecha, lat, lng)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(localeId, propuesta.nombre, propuesta.descripcion, propuesta.provincia, propuesta.subcategoria, propuesta.direccion, propuesta.enlaceGoogleMaps, propuesta.puntuacion ?? 0, propuesta.categoria, fecha, propuesta.lat, propuesta.lng);
db.prepare("DELETE FROM pendientes WHERE id = ?").run(id);
+10 -2
View File
@@ -1,7 +1,6 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import AppMovil from "./AppMovil.jsx";
import AdminPanel from "./AdminPanel.jsx";
// Enrutador mínimo sin dependencias extra
@@ -9,10 +8,19 @@ const ruta = window.location.pathname.replace(/\/$/, "");
function Root() {
if (ruta === "/admin") return <AdminPanel />;
if (ruta === "/movil") return <AppMovil />;
return <App />;
}
createRoot(document.getElementById("root")).render(
<StrictMode><Root /></StrictMode>
);
// Registro del service worker: convierte la web en una PWA instalable
// (icono en pantalla de inicio, apertura en modo standalone, shell offline).
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js").catch((err) => {
console.error("Error registrando el service worker:", err);
});
});
}