Billing and collections for a municipal water utility: clients, contracts, metered charges, payments across multiple cash registers, and the reports a collections office actually runs on.
A small water utility bills recurring service against contracts, collects across several windows, and has to answer two questions every single day: who owes us money, and does the till balance.
| Module | Covers |
|---|---|
| Clients | Full CRUD with search; a client may hold several contracts |
| Contracts | One per service point — house, business, industrial |
| Billing | Automatic generation of recurring charges |
| Payments | Quick Pay counter interface, multiple cash registers |
| Users | Admin, supervisor and collections roles |
| Reports | Debtors, till close, payment history, monthly/annual income |
Roles matter here: a collections clerk should be able to take money and not to rewrite a contract.
Client roster. One client can hold several contracts. Status is derived from their charges, so "Atrasado" is computed rather than stored and can never drift.
Screenshots are generated from this repository's own
schema.sql+demo_data.sql, so every name, address and phone number is invented.
Built as a system that handles other people's money, so the security work is in the application rather than bolted on:
| Control | Implementation |
|---|---|
| Password storage | password_hash() with PHP's default algorithm (bcrypt) |
| CSRF | Per-session token on every state-changing form |
| Brute force | Rate limiting on authentication |
| Session hijacking | Session fingerprinting |
| Response headers | CSP, X-Frame-Options, and related |
| Direct file access | .htaccess rules at the server level |
| Injection | Prepared statements throughout — 97 of them, no string-built SQL |
Details and the disclosure process in SECURITY.md.
config/config.php holds database credentials and is gitignored. The installer
generates it; config/config.example.php shows what it
produces.
Requires PHP ≥ 8.0 with pdo, pdo_mysql, mbstring, session and filter,
MySQL/MariaDB ≥ 10.3, and Apache with mod_rewrite.
git clone https://github.com/alancorrals95/aguamap.git- Create an empty database and a dedicated MySQL user — not
root. - Make
config/andlogs/writable by the web server. - Open
install.phpin a browser and follow the wizard. - Delete
install.phpandinstall_process.phpwhen it finishes. - Change the default credentials, and put the site behind HTTPS.
Step 4 is not optional. An installer left in place on a live site lets anyone reconfigure the database connection.
database/demo_data.sql loads 15 fictional clients with contracts, charges and
payments in varied states — up to date, chronically late, three months overdue — so
the reports have something meaningful to show. All names, addresses and phone
numbers are invented.
install.php web installer (delete after use)
index.php front controller
config/ generated configuration (gitignored)
includes/ shared code: db, auth, helpers
modules/ one folder per module
auth/ clients/ contracts/ billing/ payments/ users/ reports/ settings/
database/ schema.sql, seed.sql, demo_data.sql
assets/ CSS, JS, images
docs/ additional documentation
Plain PHP with PDO — no framework. For an application this size, deployed to shared hosting by people who are not developers, "upload the folder and open install.php" is worth more than a dependency manager.
MIT — see LICENSE.




