Aller au contenu

NOMVEX Apps

Votre appli dessinée avec vous, chiffrée avant de commencer, à vous pour toujours.

Demande → Étude et plan → Construction → Livraison12341. Demande2. Étude etplan3.Construction4. Livraison
NOMVEX APPS

Estimer mon appli

Écrivez votre besoin en quelques phrases. Nous repérons les fonctions déjà construites et testées, et la part qui reste à construire. Le détail technique reste disponible pour les développeurs.

1. Votre demande

Qui utilise l’appli ?
Les fonctions sont reconnues par mots-clés, sans IA : la même demande donne toujours le même plan.

2. Estimation : 20 % déjà construit et testé

Réutilisé (moteurs testés)

  • Factures conformes (TVA, mentions légales)

À construire

  • Réservation de créneaux : Créneaux, capacité, réservation et annulation sans double réservation.
  • Comptes et connexion : Rôles et droits d’accès par écran.
  • Notifications et rappels : Quand prévenir qui, et une seule fois.
  • Statistiques / tableau de bord : Indicateurs choisis et leur calcul exact.

Repéré dans votre texte : Factures conformes (TVA, mentions légales) (« factur ») · Réservation de créneaux (« reserv, creneaux ») · Comptes et connexion (« comptes ») · Notifications et rappels (« rappels, sms ») · Statistiques / tableau de bord (« statistiques »)

Estimation par mots-clés, plafonnée à 90 % ; NOMVEX confirme le vrai chiffre à l’étude. Ce n’est pas une remise de prix.

Esquisse des écrans

esquisseVotre nomProchain rendez-vousAction principaleActualitésAccueilesquisseInscriptionPrénomCatégorieConsentementEnvoyerInscriptionesquisseCette semaineCréneauxRéserverPlanningesquissePaiementMontant que vous fixezPayerPayéPaiementesquisseConversationsMessagesÉcrire…Messages
Esquisse automatique, sans IA, tirée de votre description : ce n’est pas le design final.

Livré avec chaque appli

  • 3 fichiers de tests, dont 74 contrôles d’accès43 refus vérifiés, 3 cas « seulement ses propres données » (un client ne voit que ses commandes, un parent que son enfant).
  • Export complet de vos donnéesChaque table en JSON et en CSV, à tout moment. Le code vous est remis : vous pouvez l’héberger ailleurs.
  • S’installe sur l’écran d’accueil, sans storeLes pages déjà ouvertes restent lisibles sans réseau (sous-sol, marché, chantier). Sur iPhone, les notifications demandent iOS 16.4 ou plus et l’appli ajoutée à l’écran d’accueil.
  • Bilan d’activité (subvention, rapport annuel)Totaux seulement, sans noms ; les effectifs de 1 à 4 sont masqués.

Chiffres calculés sur les fichiers de tests générés pour cette appli (voir le détail technique), pas une promesse générale.

Détail technique (pour développeurs)
  • Factures conformes (TVA, mentions légales) · lib/business/invoice.ts · tests scripts/test-invoice.ts

3. Fichiers à créer (13)

lib/apps/built/club-app/index.ts
// club-app: generated by lib/apps/compile.ts. Reused engines are imported as they are; new work lives next to this file.
import * as invoices from '../../../../lib/business/invoice';
import * as booking from './booking';
import * as accounts from './accounts';
import * as notifications from './notifications';
import * as stats from './stats';

export const engines={invoices,booking,accounts,notifications,stats};
/** Server engines, called through their existing network-service routes. */
export const serverEngines:string[]=[];
export const reusePct=20;
lib/apps/built/club-app/booking.ts
/** Slot booking / Réservation de créneaux
 * To build: Slots, capacity, book and cancel with no double booking.
 * À faire : Créneaux, capacité, réservation et annulation sans double réservation. */
export type BookingInput=Readonly<Record<string,unknown>>;
export type BookingResult={ok:boolean;errors:string[]};

export function booking(input:BookingInput):BookingResult{
  void input;
  throw new Error('not_implemented:booking');
}
lib/apps/built/club-app/accounts.ts
/** Accounts and sign-in / Comptes et connexion
 * To build: Roles and access rights per screen.
 * À faire : Rôles et droits d’accès par écran. */
export type AccountsInput=Readonly<Record<string,unknown>>;
export type AccountsResult={ok:boolean;errors:string[]};

export function accounts(input:AccountsInput):AccountsResult{
  void input;
  throw new Error('not_implemented:accounts');
}
lib/apps/built/club-app/notifications.ts
/** Notifications and reminders / Notifications et rappels
 * To build: When to notify whom, exactly once.
 * À faire : Quand prévenir qui, et une seule fois. */
export type NotificationsInput=Readonly<Record<string,unknown>>;
export type NotificationsResult={ok:boolean;errors:string[]};

export function notifications(input:NotificationsInput):NotificationsResult{
  void input;
  throw new Error('not_implemented:notifications');
}
lib/apps/built/club-app/stats.ts
/** Statistics / dashboard / Statistiques / tableau de bord
 * To build: Chosen indicators and how each is computed.
 * À faire : Indicateurs choisis et leur calcul exact. */
export type StatsInput=Readonly<Record<string,unknown>>;
export type StatsResult={ok:boolean;errors:string[]};

export function stats(input:StatsInput):StatsResult{
  void input;
  throw new Error('not_implemented:stats');
}
scripts/test-club-app.ts
import assert from 'node:assert/strict';
import {engines,reusePct} from '../lib/apps/built/club-app/index';

// Reused engines: the functions this app calls exist (their own tests cover the behaviour).
assert.equal(typeof engines.invoices.totals,'function','lib/business/invoice.ts totals');
assert.equal(typeof engines.invoices.validateInvoice,'function','lib/business/invoice.ts validateInvoice');
assert.equal(typeof engines.invoices.legalMentions,'function','lib/business/invoice.ts legalMentions');
assert.equal(reusePct,20);

// New work: each stub fails until it is built. Replace these with real cases.
assert.throws(()=>engines.booking.booking({}),/not_implemented:booking/);
assert.throws(()=>engines.accounts.accounts({}),/not_implemented:accounts/);
assert.throws(()=>engines.notifications.notifications({}),/not_implemented:notifications/);
assert.throws(()=>engines.stats.stats({}),/not_implemented:stats/);

console.log('test-club-app.ts: ok');
app/apps/built/club-app/page.tsx
export const metadata={title:'club-app | NOMVEX Apps'};

export default function Page(){
  return <main className="shell"><div className="pagehead"><h1>club-app</h1></div>
    <section className="card"><h2>Compliant invoices (VAT, legal notes)</h2></section>
    <section className="card"><h2>Slot booking</h2></section>
    <section className="card"><h2>Accounts and sign-in</h2></section>
    <section className="card"><h2>Notifications and reminders</h2></section>
    <section className="card"><h2>Statistics / dashboard</h2></section>
  </main>;
}
lib/apps/built/club-app/access.ts
// club-app: access policy generated by lib/apps/delivery-kit.ts. Deny by default; edit the rules, then re-run the access test.
import {can,type Matrix} from '../../role-matrix';

export const MATRIX:Matrix={"roles":["member","board","public"],"resources":["public-info","profile","roster","schedule","payment-status","admin"],"rules":[{"role":"public","resource":"public-info","action":"read","scope":"all"},{"role":"member","resource":"public-info","action":"read","scope":"all"},{"role":"board","resource":"public-info","action":"read","scope":"all"},{"role":"member","resource":"profile","action":"read","scope":"own"},{"role":"member","resource":"profile","action":"write","scope":"own"},{"role":"member","resource":"schedule","action":"read","scope":"all"},{"role":"member","resource":"payment-status","action":"read","scope":"own"},{"role":"board","resource":"profile","action":"read","scope":"all"},{"role":"board","resource":"profile","action":"write","scope":"all"},{"role":"board","resource":"roster","action":"read","scope":"all"},{"role":"board","resource":"roster","action":"write","scope":"all"},{"role":"board","resource":"schedule","action":"read","scope":"all"},{"role":"board","resource":"schedule","action":"write","scope":"all"},{"role":"board","resource":"payment-status","action":"read","scope":"all"},{"role":"board","resource":"payment-status","action":"write","scope":"all"},{"role":"board","resource":"admin","action":"read","scope":"all"},{"role":"board","resource":"admin","action":"write","scope":"all"}]};
export const allowed=(role:string,action:'read'|'write',resource:string,own:boolean)=>can(MATRIX,role,action,resource,own);
scripts/test-club-app-access.ts
import assert from 'node:assert/strict';
import {allowed} from '../lib/apps/built/club-app/access';

// One line per role, resource, action and record owner. "own=false" is someone else's record.
assert.equal(allowed('member','read','public-info',true),true,'member read public-info own=true');
assert.equal(allowed('member','read','public-info',false),true,'member read public-info own=false');
assert.equal(allowed('member','write','public-info',true),false,'member write public-info own=true');
assert.equal(allowed('member','write','public-info',false),false,'member write public-info own=false');
assert.equal(allowed('member','read','profile',true),true,'member read profile own=true');
assert.equal(allowed('member','read','profile',false),false,'member read profile own=false');
assert.equal(allowed('member','write','profile',true),true,'member write profile own=true');
assert.equal(allowed('member','write','profile',false),false,'member write profile own=false');
assert.equal(allowed('member','read','roster',true),false,'member read roster own=true');
assert.equal(allowed('member','read','roster',false),false,'member read roster own=false');
assert.equal(allowed('member','write','roster',true),false,'member write roster own=true');
assert.equal(allowed('member','write','roster',false),false,'member write roster own=false');
assert.equal(allowed('member','read','schedule',true),true,'member read schedule own=true');
assert.equal(allowed('member','read','schedule',false),true,'member read schedule own=false');
assert.equal(allowed('member','write','schedule',true),false,'member write schedule own=true');
assert.equal(allowed('member','write','schedule',false),false,'member write schedule own=false');
assert.equal(allowed('member','read','payment-status',true),true,'member read payment-status own=true');
assert.equal(allowed('member','read','payment-status',false),false,'member read payment-status own=false');
assert.equal(allowed('member','write','payment-status',true),false,'member write payment-status own=true');
assert.equal(allowed('member','write','payment-status',false),false,'member write payment-status own=false');
assert.equal(allowed('member','read','admin',true),false,'member read admin own=true');
assert.equal(allowed('member','read','admin',false),false,'member read admin own=false');
assert.equal(allowed('member','write','admin',true),false,'member write admin own=true');
assert.equal(allowed('member','write','admin',false),false,'member write admin own=false');
assert.equal(allowed('board','read','public-info',true),true,'board read public-info own=true');
assert.equal(allowed('board','read','public-info',false),true,'board read public-info own=false');
assert.equal(allowed('board','write','public-info',true),false,'board write public-info own=true');
assert.equal(allowed('board','write','public-info',false),false,'board write public-info own=false');
assert.equal(allowed('board','read','profile',true),true,'board read profile own=true');
assert.equal(allowed('board','read','profile',false),true,'board read profile own=false');
assert.equal(allowed('board','write','profile',true),true,'board write profile own=true');
assert.equal(allowed('board','write','profile',false),true,'board write profile own=false');
assert.equal(allowed('board','read','roster',true),true,'board read roster own=true');
assert.equal(allowed('board','read','roster',false),true,'board read roster own=false');
assert.equal(allowed('board','write','roster',true),true,'board write roster own=true');
assert.equal(allowed('board','write','roster',false),true,'board write roster own=false');
assert.equal(allowed('board','read','schedule',true),true,'board read schedule own=true');
assert.equal(allowed('board','read','schedule',false),true,'board read schedule own=false');
assert.equal(allowed('board','write','schedule',true),true,'board write schedule own=true');
assert.equal(allowed('board','write','schedule',false),true,'board write schedule own=false');
assert.equal(allowed('board','read','payment-status',true),true,'board read payment-status own=true');
assert.equal(allowed('board','read','payment-status',false),true,'board read payment-status own=false');
assert.equal(allowed('board','write','payment-status',true),true,'board write payment-status own=true');
assert.equal(allowed('board','write','payment-status',false),true,'board write payment-status own=false');
assert.equal(allowed('board','read','admin',true),true,'board read admin own=true');
assert.equal(allowed('board','read','admin',false),true,'board read admin own=false');
assert.equal(allowed('board','write','admin',true),true,'board write admin own=true');
assert.equal(allowed('board','write','admin',false),true,'board write admin own=false');
assert.equal(allowed('public','read','public-info',true),true,'public read public-info own=true');
assert.equal(allowed('public','read','public-info',false),true,'public read public-info own=false');
assert.equal(allowed('public','write','public-info',true),false,'public write public-info own=true');
assert.equal(allowed('public','write','public-info',false),false,'public write public-info own=false');
assert.equal(allowed('public','read','profile',true),false,'public read profile own=true');
assert.equal(allowed('public','read','profile',false),false,'public read profile own=false');
assert.equal(allowed('public','write','profile',true),false,'public write profile own=true');
assert.equal(allowed('public','write','profile',false),false,'public write profile own=false');
assert.equal(allowed('public','read','roster',true),false,'public read roster own=true');
assert.equal(allowed('public','read','roster',false),false,'public read roster own=false');
assert.equal(allowed('public','write','roster',true),false,'public write roster own=true');
assert.equal(allowed('public','write','roster',false),false,'public write roster own=false');
assert.equal(allowed('public','read','schedule',true),false,'public read schedule own=true');
assert.equal(allowed('public','read','schedule',false),false,'public read schedule own=false');
assert.equal(allowed('public','write','schedule',true),false,'public write schedule own=true');
assert.equal(allowed('public','write','schedule',false),false,'public write schedule own=false');
assert.equal(allowed('public','read','payment-status',true),false,'public read payment-status own=true');
assert.equal(allowed('public','read','payment-status',false),false,'public read payment-status own=false');
assert.equal(allowed('public','write','payment-status',true),false,'public write payment-status own=true');
assert.equal(allowed('public','write','payment-status',false),false,'public write payment-status own=false');
assert.equal(allowed('public','read','admin',true),false,'public read admin own=true');
assert.equal(allowed('public','read','admin',false),false,'public read admin own=false');
assert.equal(allowed('public','write','admin',true),false,'public write admin own=true');
assert.equal(allowed('public','write','admin',false),false,'public write admin own=false');
// Unknown roles and resources are always refused.
assert.equal(allowed('intruder','read','profile',true),false);
assert.equal(allowed('board','read','not-a-resource',true),false);

console.log('test-club-app-access.ts: ok (74 access checks)');
lib/apps/built/club-app/export.ts
// club-app: full data export. The user owns their data: every table, as JSON and as CSV, at any time.
import {exportCsv,type Entry} from '../../templates';

export function exportAll(tables:Readonly<Record<string,ReadonlyArray<Entry>>>){
  const csv:Record<string,string>={};
  for(const [name,rows] of Object.entries(tables))csv[name]=exportCsv(rows);
  return {json:JSON.stringify({app:'club-app',exportedAt:new Date().toISOString(),tables},null,2),csv};
}
lib/apps/built/club-app/impact.ts
// club-app: activity summary for grant dossiers and reports. Aggregates only; counts from 1 to 4 are shown as "< 5".
export {impactCells,impactCsv,MIN_CELL} from '../../impact';
public/apps/club-app/manifest.webmanifest
{
  "name": "club-app",
  "short_name": "club-app",
  "start_url": "/apps/built/club-app",
  "scope": "/apps/built/club-app",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#ffffff",
  "lang": "fr",
  "icons": []
}
public/apps/club-app/sw.js
// club-app: offline service worker. Same-origin GET only; pages fall back to the last copy when there is no network.
const CACHE='club-app-v1';
self.addEventListener('install',e=>{e.waitUntil(caches.open(CACHE).then(c=>c.add('/apps/built/club-app')));self.skipWaiting();});
self.addEventListener('activate',e=>{e.waitUntil(caches.keys().then(k=>Promise.all(k.filter(x=>x!==CACHE).map(x=>caches.delete(x)))));});
self.addEventListener('fetch',e=>{
  const r=e.request;if(r.method!=='GET'||new URL(r.url).origin!==self.location.origin)return;
  e.respondWith(fetch(r).then(res=>{if(res.ok&&res.type==='basic'){const copy=res.clone();caches.open(CACHE).then(c=>c.put(r,copy));}return res;}).catch(()=>caches.match(r).then(m=>m||caches.match('/apps/built/club-app'))));
});

Tests à lancer

S=$(mktemp -d); npx tsc --target ES2022 --module commonjs --moduleResolution node --strict --skipLibCheck --esModuleInterop --outDir $S scripts/test-invoice.ts && node $S/scripts/test-invoice.js
S=$(mktemp -d); npx tsc --target ES2022 --module commonjs --moduleResolution node --strict --skipLibCheck --esModuleInterop --outDir $S scripts/test-club-app.ts && node $S/scripts/test-club-app.js
S=$(mktemp -d); npx tsc --target ES2022 --module commonjs --moduleResolution node --strict --skipLibCheck --esModuleInterop --outDir $S scripts/test-club-app-access.ts && node $S/scripts/test-club-app-access.js

Coût sur 3 ans pour votre activité

Ajoutez les offres que vous comparez avec le prix lu sur leur page, l’adresse de cette page et la date du relevé. NOMVEX ne fournit aucun prix concurrent : une ligne sans source ou relevée il y a plus de 90 jours n’est pas tracée. Les lignes restent dans ce navigateur.

Aucune offre tracée pour l’instant.

NOMVEX : prix après étude (grille de tarifs pas encore publiée).

Comparaison établie à partir des prix que vous avez saisis, à la date indiquée, pour le même usage. Vérifiez chaque page avant de la diffuser.

Bilan d’activité (subvention, rapport annuel, banque)

Des totaux seulement, jamais de noms. Un effectif de 1 à 4 s’affiche « < 5 » pour que personne ne soit reconnaissable, surtout un mineur. Rien n’est envoyé.

Bilan
IndicateurPériode 2026-2027
Clients ou membres—
dont moins de 18 ans—
Salariés ou bénévoles—
Rendez-vous ou réservations—
Événements—

De la demande à l’application | NOMVEX Apps | NOMVEX Apps