Internal management / intranet suite for Chilean primary-healthcare establishments — CESFAM and the
rest of the APS network — built as a white-label Nextcloud deployment (official image, no
source fork), self-hosted via Docker. Each install serves one establishment, named in that
install's configuration — the product names none, ships none, and sites/ is where your own values
land.
Status: ✅ v1 done (Foundation + Spine A). Epics 0–4 are merged — dev stack + debugger + quality gate + provisioning, es-CL locale, roles/access, the four-area document tree, and live office editing — and the browser acceptance run passed on 2026-07-24.
- Is: staff-facing internal operations (documents, coordination) on Nextcloud 34 + PostgreSQL 18 +
Redis 8, with a self-hosted Euro-Office office suite and Talk for staff chat and calls.
Two ways to run it: a developer's machine via Docker Compose (this README's Quickstart), or a
clinic via APS Conecta AIO — the all-in-one installer this repo's provisioning drives
(
docs/INSTALLER.mdis that runbook; the same provisioning phases serve both). - Isn't: a clinical/patient-records system. No patient data — dev uses synthetic fixtures only.
Everything below was run on a clean checkout; each step names where it runs and how to know it worked.
Prerequisites: Docker Engine 24+ with Compose v2, make, and git. All commands run on the host
from the repo root unless noted.
-
Clone and enter the repo. A clinic installs a release tag;
mainis the development trunk and is not what goes live (ADR-0005).# standing up a clinic — pick the newest tag from the Releases page git clone --branch vX.Y.Z --depth 1 git@github.com:APS-Conecta/gestion.git apsconecta-gestion # developing on this repo git clone git@github.com:APS-Conecta/gestion.git apsconecta-gestion cd apsconecta-gestion
-
Create your local env file (gitignored — never committed).
make setup
Expected: it generates all four secrets — admin password, PostgreSQL,
OFFICE_JWT_SECRET, fixture password — writes them to.envand sets it to mode 600, so only your account can read it. Hex values, because Compose interprets$and a generated$would break the file.Your admin password lives in that file and nowhere else — why there is no rendered copy is in
CONTRIBUTING.md§ Local dev environment. Read it once into your password manager:grep '^NEXTCLOUD_ADMIN_PASSWORD=' .envIt refuses if
.envalready exists: Nextcloud reads the admin password only when it first installs itself, so rewriting the file on a live stack changes no login and only makes the file disagree with the database.Leave
SITEfor step 3 — it names the establishment this stack serves. -
Choose your establishment. None ships. The product names no establishment, so a clone carries none: you pick yours from the DEIS register of Chilean primary-care establishments, which does ship.
scripts/deis.py # search, then pick a number from the list scripts/deis.py cesfam <comuna> # or filter — every term must match, accent-blind scripts/deis.py <codigo> --new mi-establecimiento # writes sites/mi-establecimiento/site.sh
Any primary-care establishment in the register works — CESFAM, PSR, CECOSF, CGR, CGU, COSAM, SAPU, SAR or SUR. The
cesfamabove is a search word, not a required type.Expected: the last command asks for your sectors and programs — one per line, blank line to finish — because no register knows them. Then set
SITE=mi-establecimientoin.env. Everything else about the establishment (folders, the access matrix) is in that file, and it is yours to edit. That file is gitignored, like.env: your configuration stays yours, and it survives agit pulluntouched.If you skip this,
make installstops before starting anything. It prints the commands above whenSITEnames an establishment that has nosites/<slug>/site.sh; whenSITEis unset entirely it points you back at.env, which is this step's other half. -
Install it. One command: it starts the stack, waits for Nextcloud's own installer to finish, provisions everything, and health-checks the result.
make install
Expected: one line per phase, then a summary naming your establishment and its counts:
▸ stack ▸ provisioning — full log: .install.log security jobs … ✓ <your establishment> — N teams, N group folders, N grants health: PASS http://localhost:8180Run it again whenever you edit
sites/<slug>/site.shorgit pull— it converges, and everything already applied is skipped in seconds. It deliberately does not move the Nextcloud image, and it converges apps onto the vendored tarball rather than onto whatever is newest (#117) — bumping either stays a separate, deliberate act. If a phase fails, the last 20 log lines are printed and the whole command is the retry.It adds and never deletes. Take a sector out of
site.shand the group and its folder stay put — the run ends by naming them and the command that would remove them, and you decide (#85). Deleting a group folder deletes its files, and a typo in a data file must not be able to do that.make divergenceasks the same question on demand. -
Open the app. Browse to
http://localhost:8180(theHTTP_PORTfrom your.env) and sign in with theNEXTCLOUD_ADMIN_USER/NEXTCLOUD_ADMIN_PASSWORDyou set. You now have a running instance.
To stop: make down (keeps your data volumes). That's the whole loop.
A clinic does not run the dev stack. It runs APS Conecta AIO: one docker run from the
published suite tag, the branded es-CL wizard, and this repo's aps-conecta host bundle driving
the same provisioning phases over docker exec. The whole path — preflight, wizard,
Provisionador, timers, backups, the map — is docs/INSTALLER.md (English
runbook) with the Spanish walkthrough in docs/GUIA-CLINICA.md.
One establishment per install (D13); the suite tag equals this repo's release tag (D12).
Run make help — it reads the Makefile, so it cannot drift. (A hand-copied table used to live
here and had already lost four targets.)
Step-debugging: make up-dev, then in VS Code run the committed "Listen for Xdebug" config
(.vscode/launch.json, port 9003) and send a request carrying the Xdebug trigger.
Office suite: Euro-Office comes up with the stack and make install wires the connector — nothing extra
to run (#81). make office-smoke checks the pipe end to end and audits the image (OSS, no paid licence);
make office-down stops just the document server when you want the ~2.5 GB back.
Live editing acceptance (Epic 4) — done, in a browser on 2026-07-24
(#30, closed). What was run and what it proved:
ROADMAP.md § Where we are.
Which formats you can actually edit. OOXML — docx, xlsx, pptx — opens and edits normally.
ODF — odt, ods, odp — is editable too, through conversion, so expect some formatting loss on
save: the connector declares those lossy-edit rather than edit. Enabled deliberately (#45,
B-007) because the alternative — converting to .docx by hand — loses the same fidelity and leaves a
duplicate file behind, which docs/CONVENTIONS.md § Una sola copia viva
exists to prevent. Set by provisioning/phases/14-office.sh, never in the admin UI (AD-2).
| Path | What |
|---|---|
compose.yaml |
Every service — nextcloud/db/redis/cron + eurooffice (no longer a profile, #81). All four images pinned by digest (#109). |
compose.dev.yaml, Dockerfile.dev, dev/xdebug.ini |
The derived Xdebug dev image (AD-10). |
.env.example |
Template for your gitignored .env. Never commit .env. |
Makefile |
The dev lifecycle (make help). |
scripts/ |
env-init.sh (make setup) + install.sh (the one command) + wait-ready.sh, test.sh + smoke.sh (the gate), seed-idempotent.sh, office-smoke.sh, divergence.sh (what is live but undeclared), image-digests.sh + app-versions.sh (is anything we pinned behind upstream), deis.py, and env.sh (shared preamble). |
provisioning/ |
The single idempotent provisioning writer: seed.sh runner, lib.sh guard helpers, phases/05-60, apps/ (per app: the vendored tarball, its VENDOR file and its patches — ADR-0002), and provisioning/README.md. |
sites/ |
The DEIS register of primary-care establishments, tracked — it is what you pick from. Your <slug>/site.sh (teams, folders, ACL matrix, identity) lands here and is gitignored: it depends on which establishment you install, so it is install-local like .env. No establishment ships; write yours with scripts/deis.py. |
host/ |
The clinic-side host bundle: aps-conecta (preflight / run-command / provision / revalidate / respaldo / tiles / datos) + the weekly re-provision and monthly tiles-refresh systemd units. A clinic installs it from the release page — docs/INSTALLER.md. |
apps/, themes/ |
Live-mounted. apps/ is gitignored and holds the apps unpacked from the tarballs committed in provisioning/apps/, patched at seed time (ADR-0002); themes/apsconecta/ is the white-label server theme. |
docs/ARCHITECTURE.md |
The architecture overview (design SSOT). |
docs/INSTALLER.md · docs/GUIA-CLINICA.md |
The clinic runbook (English) · the Spanish clinic-IT guide — the installer era's operator docs. |
ROADMAP.md · BUGS.md |
Roadmap narrative · known bugs. Work in progress is on the Projects board. |
LICENSE · docs/LICENSING.md |
Our code's license (AGPL-3.0-or-later) · full third-party license audit. |
CONTRIBUTING.md · AGENTS.md · CONTRIBUTORS.md |
Contribution rules + how we track work · AI-agent invariants · the team. |
.github/ |
CODEOWNERS, PR + issue templates, SECURITY.md. |
The paradigm is vanilla Nextcloud + configuration-as-code, no fork: the platform owns runtime and data;
this repo adds only declarative customization (config, theming, groups/folders/ACLs) — no core patch,
zero custom PHP in v1 — and the running instance is a disposable projection of the repo's recipe. The
only thing that changes instance state is make seed, which make install wraps — one idempotent occ
script (AD-2). Never hand-click
configuration into the running app; if it isn't scripted, it isn't real.
- Edit the recipe — a
provisioning/phases/NN-*.sh,sites/<slug>/site.sh,apps//themes/, or.env. make install— converge. Same command as the first time; it is idempotent by construction. (make up-devfirst if you want Xdebug on:9003;SEED_FIXTURES=0 make seedapplies structure only, skipping the fixture phases;make seedis the verbose inner pipeline.)make smoke/make test— health-gate + the local quality gate. Green before a PR — CI runs the same script.- Open a PR — see
CONTRIBUTING.md(GitHub Flow, Conventional Commits,ai-assisted, self-review); work is tracked on the Projects board.
Features are applied by numbered scripts in provisioning/phases/, run in fixed order 05 → 60 by
make seed (structure before fixtures). One epic owns one file (see each file's # OWNER: header) — a
new epic adds its own NN-*.sh and seed.sh picks it up automatically, so parallel epics never collide.
Its body uses only the query-before-create guard helpers, so re-running converges instead of
duplicating. Never blind-create. Verify by re-running make seed (every line should log "exists" /
"already =") then make test.
The phase list, the contract and the full helper list live in
provisioning/README.md — one owner per fact, so they are not repeated here.
apps/ (→ custom_apps) and themes/ are bind-mounted for live edit — no rebuild, no fork; make up
runs make fix-mount-perms so the container (uid 33) can write them. A custom app talks to Nextcloud only
through OCP public APIs (OCP\…) — never patch core (AD-9) — carries an appinfo/info.xml
(min-version="34"), and is enabled with occ app:enable <id>. One custom app lives here,
epidemiologia, declared in OWN_APPS and unpacked from its own vendored tarball like every other app
(ADR-0003, reversing AD-1) — so an install still needs no
network. On a development machine the same directory is a git clone instead, and provisioning leaves it
alone. All of apps/ is gitignored either way. The REM analyzer is the next Layer-2 app, on the same terms. White-labeling ships as the themes/apsconecta/
server theme — AD-6's config-only rule is superseded by
ADR-0001. How the theming actually behaves (and why most of
it is config rather than CSS) is docs/THEMING-MODEL.md; How to apply the brand to an instance, step by step, is docs/BRANDING.md.
Neither is a hosting guide: the clinic hosting runbook is
docs/INSTALLER.md (the AIO installer era); the dev stack stays this
README's Quickstart. Locale stays in the 10-locale phase.
Euro-Office (AD-5) is part of the stack, not an add-on: #81 dropped its compose profile, so make install
yields a clinic that can open a document. make office-smoke runs the editing smoke and audits the image
provenance (OSS, no paid licence); it is also part of make test now that the service is always up.
Defined once in AGENTS.md (the invariants) and docs/ARCHITECTURE.md
(the design paradigm). Read them before you touch the stack.
See CONTRIBUTING.md — GitHub Flow + the PR review gate, principles
(DRY/SOLID/YAGNI), the language split (code in English, UI in Spanish), and the documentation rules.
The committed single source of truth for the design is docs/ARCHITECTURE.md
(the architecture overview); the code itself (provisioning/phases/, compose.yaml) is authoritative for
behavior. Status narrative lives in ROADMAP.md.
| Topic | Context7 library ID |
|---|---|
| Nextcloud admin / deploy | /websites/nextcloud_server_admin_manual |
| Nextcloud app development | /websites/nextcloud_server_developer_manual |
| Nextcloud PHP / OCP API | /websites/nextcloud-server_netlify_app |
| Nextcloud Vue UI kit | /nextcloud-libraries/nextcloud-vue |