feat(database): add the RD organization columns and the territorial catalog - #612
Open
mrivas00 wants to merge 4 commits into
Open
feat(database): add the RD organization columns and the territorial catalog#612mrivas00 wants to merge 4 commits into
mrivas00 wants to merge 4 commits into
Conversation
mrivas00
force-pushed
the
feat/mati/rd-schema-additions
branch
2 times, most recently
from
August 24, 2026 15:10
a015d35 to
fb37631
Compare
mrivas00
force-pushed
the
feat/mati/rd-terminology
branch
from
August 24, 2026 15:10
b0887b8 to
8821621
Compare
mrivas00
force-pushed
the
feat/mati/rd-terminology
branch
from
August 24, 2026 17:58
8821621 to
a02887a
Compare
mrivas00
force-pushed
the
feat/mati/rd-schema-additions
branch
from
August 24, 2026 17:58
fb37631 to
144ca97
Compare
mrivas00
force-pushed
the
feat/mati/rd-terminology
branch
from
August 26, 2026 12:12
a02887a to
781cc21
Compare
mrivas00
force-pushed
the
feat/mati/rd-schema-additions
branch
from
August 26, 2026 12:12
6152fb0 to
80c901f
Compare
mrivas00
force-pushed
the
feat/mati/rd-terminology
branch
from
August 26, 2026 12:36
781cc21 to
23ce2b3
Compare
mrivas00
force-pushed
the
feat/mati/rd-schema-additions
branch
from
August 26, 2026 12:36
80c901f to
ee08ab5
Compare
…atalog One additive migration for the whole RD stack, so no later pull request competes for a timestamp: - secondary_subsector_id on organization_data: a second reference into the same activity catalog, allowed to sit in a different sector. - territory: the administrative hierarchy as one self-referencing table, plus territory_id on organization_data pointing at the most specific node the registrant knows. Ancestors are derived by walking parent_id, never stored on the organization, which makes an incoherent combination (a municipality outside its province) impossible to represent. Both columns are nullable and the new table starts empty, so the migration applies to a populated database without rewriting a row. It also loads the ten planning regions and the 32 provinces. seed.ts skips entirely once a country exists, so an already-populated deployment — the RD test environment — would never receive the catalog by seeding. The insert is guarded on an empty table rather than ON CONFLICT, because the unique index spans (parent_id, level, name) and Postgres treats NULL parents as distinct, so it would not deduplicate the roots. Both foreign keys are RESTRICT rather than Prisma's default SET NULL: nulling a deleted node's parent would promote its children to roots, where they would surface in the top-level selector as planning regions, and nulling an organization's reference would rewrite a location it declared. The hierarchy gets no CRUD endpoints and no maintainer screen — boundaries change by law, not by administrative action — but it is an ordinary catalog table, so adding one later is additive.
Extends the base Zod schemas with secondarySubsectorId and territoryId, adds a TerritoryBaseSchema for the hierarchy, and re-exports the TerritoryLevel enum.
The insert followed the regionalization of Decreto 710-04, which the Ley Organica de Regiones Unicas de Planificacion (num. 345-22, G. O. 11077 del 2 de agosto de 2022) replaced. Azua moves from Valdesia to El Valle -- which takes Valdesia from four provinces to three and El Valle from two to three -- and four rows take the wording the ministry validates against: Baoruco becomes Bahoruco, Monte Cristi becomes Montecristi, Higuamo becomes Higuamo with the dieresis, and `Ozama o Metropolitana` becomes Ozama. It also gains the 157 municipios. Article 7 of the same law lists them under each province, so the level that was deferred waiting on the ONE catalog turns out to need nothing external. Their join needs no region: province names are unique across the country. The migration has not been applied to the RD environment, so this edits the insert in place rather than adding a corrective one.
mrivas00
force-pushed
the
feat/mati/rd-terminology
branch
from
August 26, 2026 20:21
23ce2b3 to
56e56e4
Compare
mrivas00
force-pushed
the
feat/mati/rd-schema-additions
branch
from
August 26, 2026 20:21
ee08ab5 to
87aaa3f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two of MMARN's observations need columns that do not exist: a secondary economic activity, and a five-level territorial location. Nothing else in the ten needs schema — the rest is copy or seed data.
They land as one migration, early in the stack, so no later pull request carries one and no two compete for a timestamp. Nothing consumes these columns yet — PRs 7 and 8 do.
PR 3 of 8 · base
feat/mati/rd-terminology(PR 2) · the only migration in the stack.What
organization_data.secondary_subsector_idBIGINTnullable →country_subsector,RESTRICTorganization_data.territory_idBIGINTnullable →territory,RESTRICTterritoryterritory_levelenum, self-referencingparent_idterritoryPlus the matching Zod base schemas and a
TerritoryBaseSchema.Review notes
The migration carries data, which nothing in this repository did before.
seed.tsskips entirely once a country exists, so the RD test environment — already populated — would never receive the territorial catalog by seeding. The migration inserts it;seedTerritories(PR 7) stays as the path for a database created without migrations and returns early when the table already holds rows. The branch is terminal and the target deployment is populated, which is what justifies the departure; flagging it because it should be a decision, not a surprise in review.The insert is guarded on an empty table, not
ON CONFLICT. The unique index spans(parent_id, level, name)and Postgres treatsNULLparents as distinct, so it does not deduplicate the ten roots. The two child levels resolve their parent by name and useON CONFLICT DO NOTHING; province names are unique across the country, so the municipal join needs no region.The catalog is the law's, and it corrected five rows. Article 7 of the Ley Orgánica de Regiones Únicas de Planificación (núm. 345-22, G. O. 11077 del 2 de agosto de 2022) enumerates regions, provinces and municipios in one place. The first draft of this insert followed Decreto 710-04, which that law replaced: Azua sat under Valdesia instead of El Valle, and four rows carried wording the ministry does not use. The municipal level was going to wait on an ONE catalog it turns out not to need.
docs/development/rd-territories-sources.md(PR 7) records the source, the ONE's corroborating counts and why the two levels below the municipality are still empty.Applies to a populated database. Both columns are nullable and
territorystarts empty, so no existing row is rewritten: organizations getNULLfor both new references.Two decisions worth a look:
territorystores one reference per organization, not five columns. Ancestors are derived by walkingparent_id. That makes an incoherent combination — a municipality outside the selected province — structurally impossible, at the cost of a recursive traversal when filtering by province. The consumer today is admin reporting; a closure table is the escape hatch if dashboards ever query it per request.RESTRICT, not Prisma's defaultSET NULL. Nulling a deleted node's parent would promote its children to roots, where they would surface in the top-level selector as planning regions; nulling an organization's reference would rewrite a location it declared, which this repository's catalogs never do.No maintainer, but an ordinary table. No CRUD endpoints and no maintainer screen: administrative boundaries change by law, not by administrative action. It is a normal catalog table, so a deployment that later needs to correct it can add a maintainer additively — endpoints, a screen, and a
statuscolumn if it wants soft-delete. Those two columns are deliberately not added now: nothing reads them.secondary_subsectorneeded both relations toCountrySubsectornamed (organization_data_subsector/organization_data_secondary_subsector). Relation names do not affect the FK constraint names Prisma generates, so the existingorganization_data_subsector_id_fkeyis untouched.Verification
pnpm format && pnpm lint && pnpm type-checkgreen,pnpm test:web746 passing, full API suite 2,304 passing.prisma validateclean, andprisma generateproduces the client the type-check ran against.The migration has now been applied, twice, both ways.
Against an empty database — the API test harness deploys every migration and runs the whole seed into a fresh Postgres container on each run, so all 226 test files exercise a database this migration built.
Against a populated one, which is what task 4.6 actually asked and the harness cannot cover since it always starts empty: on a throwaway Postgres 18.6, every migration up to
20260819120000was deployed, twoorganization_datarows inserted, and then this migration applied. Result:NULLinsecondary_subsector_idandterritory_idand every other column untouched;INSERTstatements is a complete no-op, so the empty-table guard on the roots and the twoON CONFLICT DO NOTHINGclauses below it both hold.Tasks 4.6 and 2.4 are closed.