Tableau de bord
Clients actifs
Ce mois
Visites planifiees
Cette semaine
Compromis signes
Ce mois
Dossiers en attente
A relancer
Clients recents
ClientType de projetBudgetStatutEtapePage client
◈ Activite recente

Aucune activite

◷ Agenda de la semaine

Aucun rendez-vous

Mes clients
Chargement...
Creer un dossier client
Biens
OrdreBienSecteurPrix CHFPiecesStatut clientActions
Chargement...
Agenda des visites
Toutes les visites
Trier par
Documents
Chargement...
01 · Projet

Vue d'ensemble

Wolff Immobilier est une plateforme web pour une agence immobiliere de luxe. Elle propose deux interfaces distinctes : une zone client personnalisee par dossier, et un dashboard pour les agents.

Identite visuelle

Theme
Noir & Or (luxe immobilier)
Couleurs cles
Noir #0a0a0a · Or #c9a84c · Creme #faf8f3
Typographie
Cormorant Garamond (titres) · DM Sans (texte)

Deux interfaces

Page client (publique, par client) — chaque client a sa propre URL avec ses biens, ses visites, ses statuts (a visiter / coup de coeur / retire), et ses documents.

Dashboard agent (avec login) — gestion des clients, des biens par recherche, du planning de visites, et de tous les documents.

URLs

Site public
https://wolff-immobilier.com
Page client
https://wolff-immobilier.com/clients/<slug>/
Dashboard agent
https://agent.wolff-immobilier.com
02 · Infrastructure

Stack technique

Hebergement

Serveur
Hetzner Cloud · Ubuntu 24.04 LTS
IP publique
77.42.95.132
Acces
SSH (port 22, root)
Localisation
Allemagne

Runtime

Node.js
v20 LTS (sans dependance npm externe)
Gestionnaire
PM2 (process name : wolff)
Port interne
3000 (HTTP localhost)
Stockage
JSON local /root/wolff-immobilier/data/db.json

Reverse proxy & SSL

Nginx
v1.24, deux sites enabled : agent-wolff, wolff-immobilier
SSL
Let's Encrypt via Certbot (renouvellement automatique)
Config sites
/etc/nginx/sites-enabled/

DNS

Registrar
Infomaniak
Domaines
wolff-immobilier.com · agent.wolff-immobilier.com

Securite

  • UFW : ports ouverts 22 (SSH), 80 (HTTP), 443 (HTTPS)
  • JWT : authentification dashboard (HMAC-SHA256, 8h)
  • Passwords : hash PBKDF2-SHA512 + sel
  • Indexation : desactivee (robots.txt + header X-Robots-Tag)
03 · Structure

Architecture des fichiers

Tout le projet vit dans /root/wolff-immobilier/ sur le serveur.

/root/wolff-immobilier/
├── server.js              # Serveur HTTP principal (routeur, API, fichiers statiques)
├── db.js                  # Acces a la base JSON + crypto (JWT, hash)
├── generator.js           # Genere les pages HTML clients au demarrage
├── README.md              # Documentation
├── data/
│   └── db.json            # Base de donnees JSON (seule source de verite)
├── public/
│   └── dashboard.html     # Interface dashboard agent (1 fichier autonome)
├── clients/               # Pages HTML clients generees
│   ├── vincent/index.html
│   └── jeremy-barnier/index.html
└── uploads/               # Fichiers uploades (PDF, images des biens)

Roles des fichiers

server.js
Le coeur. Routeur HTTP, toutes les routes API, sert les fichiers statiques. ~715 lignes.
db.js
Fonctions load(), save(), hash(), verifyJWT(). Lit/ecrit data/db.json.
generator.js
Au demarrage du serveur, regenere clients/<slug>/index.html pour chaque client en base.
dashboard.html
App mono-fichier (HTML+CSS+JS inline). Aucun framework. ~1424 lignes.
clients/<slug>/
Page autonome generee pour chaque client. Lit l'API publique sans auth.
Important Le tableau data.biens (legacy, vide) doit rester present dans db.json car regenerateAll() dans server.js y accede au demarrage. Ne pas le supprimer.
04 · Backend

Routes API

Toutes les routes utilisent le pattern /api/.... Les routes protegees attendent un header Authorization: Bearer <jwt>.

Authentification

MethodeRouteDescription
POST/api/auth/loginLogin agent, retourne JWT
GET/api/auth/meInfo agent connecte (token requis)

Dashboard

MethodeRouteDescription
GET/api/dashboard/statsKPI : clients actifs, visites semaine, compromis...
GET/api/dashboard/activites20 dernieres activites
GET/api/dashboard/agendaToutes les visites enrichies (avec client_nom)

Clients

MethodeRouteDescription
GET/api/clientsListe tous les clients
GET/api/clients/:slugDetail d'un client
POST/api/clientsCreer un client (cree aussi /clients/<slug>/)
PUT/api/clients/:slugModifier un client
DELETE/api/clients/:slugSupprimer un client (et son dossier)

Biens (routes generiques par slug)

MethodeRouteDescription
GET/api/biens-:slugTous les biens d'un client (avec auth)
POST/api/biens-:slugCreer un bien (ordre = max+1 auto)
PUT/api/biens-:slug/:idModifier un bien
DELETE/api/biens-:slug/:idSupprimer un bien
PUT/api/biens-:slug-ordreReorder (drag/drop)
GET/api/biens-public/:slugListe publique (sans auth, page client)
GET/api/biens-tousAgregation tous clients (KPI/agenda)
GET/api/biens-client/:slugStatuts des biens d'un client
GET/api/biens-tous-clientsStatuts tous clients

Recherches

MethodeRouteDescription
GET/api/recherches/:slugRecherches d'un client
POST/api/recherches/:slugCreer recherche
PUT/api/recherches/:slug/:ridModifier recherche
DELETE/api/recherches/:slug/:ridSupprimer recherche
GET/api/recherches/:slug/:ridBiens d'une recherche (filtres)

Visites

MethodeRouteDescription
GET/api/visitesToutes les visites (auth)
POST/api/visitesCreer une visite
PUT/api/visites/:idModifier (date, heure, statut)
DELETE/api/visites/:idSupprimer
GET/api/visites-public?slug=XVisites publiques (page client)

Documents

MethodeRouteDescription
GET/api/documents-bien/:idListe docs d'un bien
POST/api/documents-bien/:id/uploadUpload (multipart, champ agent_only)
DELETE/api/documents-bien/:id/:docIdSupprimer un document
05 · Donnees

Structure de la base de donnees

Un seul fichier JSON : /root/wolff-immobilier/data/db.json. Lu/ecrit a chaque requete via db.load() / db.save().

Cles principales

CleTypeRole
agentsarrayComptes agents (login dashboard)
clientsarrayClients (Vincent, Jeremy...) avec slug, dossier, etape
etapesarrayCatalogue des 14 etapes possibles d'un dossier
activitesarrayJournal d'activites du dashboard
biens_<slug>arrayBiens d'un client (un tableau par client : biens_vincent, biens_jeremy_barnier...)
biens_client_statutsobjectStatuts (coup-de-coeur, retire) par client puis par bien_id
recherchesobjectRecherches par client : { "vincent": [...], "jeremy-barnier": [...] }
statuts_recherchesobjectFiltres avances de chaque recherche
visitesarrayToutes les visites (tous clients)
documents_biensobjectDocuments par bien_id (cle = bien_id, valeur = array)
biens / documents / messagesarray (vide)Legacy, NE PAS SUPPRIMER (utilises par regenerateAll)

Schema d'un bien

{
  "id": 1772548615726,           // timestamp Date.now()
  "ordre": 0,                    // unique par client, sert au tri et numerotage (#ordre+1)
  "recherche_id": "r1",          // appartient a quelle recherche
  "titre": "Appartement 3.5p",
  "prix": "920'000",
  "secteur": "Montreux",
  "pieces": "3.5",
  "surface": "82",
  "etage": "2e",
  "annee": "1985",
  "travaux": "non",
  "agence": "iad",
  "contact": "+41 79 XXX",
  "lien": "https://...",
  "note_agent": "..."
}

Schema d'une visite

{
  "id": 1773575578649,
  "client_slug": "vincent",
  "bien_id": "1800000000005",
  "date": "2026-03-16",          // ISO YYYY-MM-DD
  "heure": "11:30",              // HH:MM
  "adresse_rdv": "Route de ...",
  "statut_visite": "programmee", // programmee | visitee | retenu | controle
  "note_agent": "",
  "created_at": "2026-03-15T11:52:58.649Z"
}
06 · Logique

Regles metier

Numerotation des biens

Chaque bien a un champ ordre (entier). Le numero affiche partout est ordre + 1 (ex : #5).

  • Unique globalement par client — pas par recherche. Vincent a 55 biens numerotes #1 a #55, toutes recherches confondues.
  • Calcule a la creation : ordre = max(ordres existants) + 1 dans la route POST.
  • Affichage : utiliser b.ordre + 1 partout (jamais indexOf ni i+1).
  • Reorder : drag/drop met a jour le champ ordre via PUT /api/biens-:slug-ordre.

Multi-clients / multi-recherches

  • Architecture 100% slug-based. Aucun client n'est hardcode dans le code.
  • Dashboard utilise _clientBiensActif (variable JS globale) pour savoir quel client est selectionne.
  • Un client peut avoir plusieurs recherches (ex : Vincent a "Riviera vaudoise" et "Nyon/Rolle").
  • L'onglet Biens du dashboard a un selecteur de client + onglets de recherches.

Statuts d'un bien (cote client)

StatutDescription
a-visiterPar defaut (non choisi)
coup-de-coeurLe client a marque ce bien comme favori
retireLe client a ecarte ce bien

Statuts d'une visite

StatutDescription
programmeeVisite future planifiee
visiteeVisite effectuee
retenuCoup de coeur confirme apres visite
controleVisite de controle a replanifier

Visibilite des documents

  • Un document a un champ agent_only (boolean).
  • agent_only: true = visible uniquement dans le dashboard, jamais sur la page client.
  • agent_only: false = visible client (telechargeable depuis la fiche bien).
07 · Operations

SSH & operations courantes

Se connecter au serveur

# Connexion SSH (mot de passe demande, non documente ici)
ssh root@77.42.95.132

# Aller dans le projet
cd /root/wolff-immobilier
Bonne pratique Utiliser une cle SSH plutot que le mot de passe. Generer une cle avec ssh-keygen -t ed25519 et l'ajouter via ssh-copy-id root@77.42.95.132.

Backup AVANT chaque modification

Obligatoire Toujours faire un backup avant de toucher au code ou a la base. Une commande, une seconde, peut sauver des heures.
tar -czf /root/wolff-backup-$(date +%Y%m%d-%H%M).tar.gz /root/wolff-immobilier

Lister les backups

ls -lh /root/wolff-backup-*.tar.gz

Restaurer un backup

# Renommer l'actuel avant de restaurer
mv /root/wolff-immobilier /root/wolff-immobilier.broken

# Extraire le backup
tar -xzf /root/wolff-backup-20260512-1647.tar.gz -C /

# Restart
pm2 restart wolff

Restart du serveur applicatif

pm2 restart wolff
pm2 status
pm2 logs wolff --lines 10 --nostream

Restart Nginx

nginx -t                  # tester la config
systemctl reload nginx    # appliquer sans coupure
systemctl restart nginx   # redemarrer franchement

Update du systeme

apt update
DEBIAN_FRONTEND=noninteractive apt upgrade -y -o Dpkg::Options::="--force-confold"
apt autoremove -y

# Si reboot requis
[ -f /var/run/reboot-required ] && echo "REBOOT REQUIS"
reboot

Verifier le port et le service

# Le port 3000 doit etre en ecoute par node
ss -tlnp | grep 3000

# Le service systemd PM2 doit etre actif
systemctl is-active pm2-root
08 · Diagnostic

Commandes de depannage

Logs PM2

# Logs en direct
pm2 logs wolff

# Dernieres lignes seulement
pm2 logs wolff --lines 20 --nostream

# Vider les logs
pm2 flush wolff

Verifier la syntaxe du code

# server.js
node --check /root/wolff-immobilier/server.js

# JS dans dashboard.html (extraire avant)
SS=$(grep -n "^<script>" /root/wolff-immobilier/public/dashboard.html | head -1 | cut -d: -f1)
SE=$(grep -n "^</script>" /root/wolff-immobilier/public/dashboard.html | tail -1 | cut -d: -f1)
sed -n "$((SS+1)),$((SE-1))p" /root/wolff-immobilier/public/dashboard.html > /tmp/dash.js
node --check /tmp/dash.js

Tester l'API en live

# Obtenir un token
TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \
  -d '{"email":"agent@wollf.fr","password":"PASSWORD"}' \
  http://localhost:3000/api/auth/login | grep -oP '"token":"[^"]+' | cut -d'"' -f4)

# Tester une route protegee
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/biens-vincent

Inspecter la base

# Voir toutes les cles + tailles
node -e "
const db = require('/root/wolff-immobilier/data/db.json');
Object.keys(db).forEach(k => {
  const v = db[k];
  console.log(k, Array.isArray(v) ? v.length+' items' : typeof v);
});"

# Voir les visites
node -e "
const db = require('/root/wolff-immobilier/data/db.json');
db.visites.forEach(v => console.log(v.date, v.heure, v.client_slug, v.bien_id));"

Modifier la base proprement

node << 'EOF'
const fs = require('fs');
const path = '/root/wolff-immobilier/data/db.json';
const db = JSON.parse(fs.readFileSync(path, 'utf8'));

// Modification ici, par exemple :
// db.visites = db.visites.filter(v => v.date);

fs.writeFileSync(path, JSON.stringify(db, null, 2));
console.log('OK');
EOF

Disque, memoire, processus

df -h /              # espace disque
free -h              # memoire
pm2 monit            # monitor interactif
htop                 # vue d'ensemble
09 · Attention

Pieges a eviter (FAQ developpeur)

Piege 1 — Ne JAMAIS supprimer data.biens / data.documents / data.messages Meme si ces tableaux sont vides (legacy), regenerateAll() dans server.js y accede au demarrage. Les supprimer = le serveur crashe au boot.
Piege 2 — Routes biens : toujours genericiser sur le slug Ne JAMAIS hardcoder un client dans une route. Utiliser '/api/biens-' + slug. La cle DB associee est 'biens_' + slug.replace(/-/g,'_') (Jeremy-Barnier devient biens_jeremy_barnier).
Piege 3 — Numero de bien : ordre+1, jamais indexOf ni i+1 Le numero affiche doit toujours etre b.ordre + 1. Utiliser indexOf(bien)+1 ou (b, i) => i+1 casse la coherence entre dashboard et page client, et entre recherches.
Piege 4 — Creer un bien : ordre = max+1 (pas length) Dans la route POST, calculer ordre = Math.max(ordres existants) + 1. Utiliser data[key].length cree des doublons d'ordre des qu'on supprime un bien.
Piege 5 — KPI dashboard : utiliser data.visites, pas data.biens Le KPI visites_semaine dans /api/dashboard/stats doit filtrer data.visites. Filtrer data.biens (legacy vide) retourne toujours 0.
Piege 6 — Python regex multiligne sur server.js Les tirets unicode ── (U+2500) dans les commentaires posent souci en regex Python multiline. Preferer la suppression par numero de ligne (lines[:N] + lines[M:]) pour les gros blocs.
Piege 7 — Python : verifier la taille apres ecriture Toujours valider len(s) > N avant d'ecrire un fichier. Une regex qui rate peut produire un fichier vide. Faire sys.exit(1) si la taille est suspecte.
Piege 8 — Visites sans date Une visite avec date: "" casse les tris (localeCompare) et n'apparait nulle part. Toujours valider la date a la creation. Nettoyer la base si besoin : db.visites = db.visites.filter(v => v.date && v.heure).
Piege 9 — Generator.js utilise /api/biens-public/:slug Si la route /api/biens-public/:slug est retiree, tous les nouveaux clients crees auront une page HTML cassee. Ne pas la supprimer.

Outils utiles

  • Toujours backup avant modif (commande en section 07)
  • Verifier syntaxe : node --check fichier.js
  • Lire les logs apres chaque restart : pm2 logs wolff --lines 10 --nostream
  • Tester routes avec curl avant de modifier le front

Conventions de code

  • Routes API : pattern slug-based (/api/<ressource>-:slug ou /api/<ressource>/:slug)
  • Dashboard : variables d'etat globales prefixees par _ (ex : _clientBiensActif, _visites)
  • Style : Noir & Or, typographie Cormorant + DM Sans, jamais d'emoji dans l'UI (utiliser des symboles unicode discrets : ◈ ◎ ⌂ ◷ ◻ ⚙)
  • Numero de bien : toujours #<ordre+1>, dans une div en font-size:10px, color:gris, letter-spacing:1px