The docs still described the state before the last five commits: the insurance spec called for a `@Cron` literal and a manual mark-as-sent mutation, PLAN.md had step 12 as "NOT STARTED", and README's module and route lists predated seven modules. - MASS_EMAIL_NOTIFICATIONS.md: new "Send flags", "API surface" and "Scheduled runs" sections; "Cron (future)" removed — it exists. The flags table says which flags apply where, and why a debug renewal send must skip both the RenewalNotice row and `lastSuccessfulAt`. - INSURANCE_FEATURES_SPEC.md: §1 BUILT note listing the three places the build diverged from the spec; §1.1 and §1.4 marked superseded in place rather than deleted, so the reasoning stays readable. - PLAN.md: step 12 renewal emails DONE with the divergences; status paragraph rewritten. - README.md: current module/route lists, plus a "Scheduled jobs" section — a reader cloning this repo had no way to know the API sends mail on a timer. - DEPLOY_AND_MIGRATIONS.md: the cadence lives in app_settings and survives an image rollback, and the servicios sweep has no multi-replica lock. - RESUME.md: session record for the whole notificaciones arc. - RENEWAL_NOTICES.md: pointer that this is the legacy record, not what shipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
232 lines
7.7 KiB
Markdown
232 lines
7.7 KiB
Markdown
# Jorge Cuadros & Asociados — Platform
|
|
|
|
Internal platform for a Baja California insurance brokerage and property-services
|
|
firm: a single expedient joining each client's **properties/services**,
|
|
**insurance policies**, **account statement**, and the firm's **checkbook**.
|
|
It replaces a legacy PHP/Access app (see `RESUME.md` and `PLAN.md` for the full
|
|
history and rebuild rationale).
|
|
|
|
The UI is Spanish-first; the codebase and this document are in English.
|
|
|
|
---
|
|
|
|
## Stack
|
|
|
|
| Layer | Tech | Port |
|
|
| --------- | -------------------------------------------------------------- | ---- |
|
|
| Web | Next.js 14 (App Router, React 18) | 3000 |
|
|
| API | NestJS 10 · Passport local + `express-session` · Argon2 | 3001 |
|
|
| Database | MySQL 8 via Prisma 5 (`@jorgecuadros/database` workspace pkg) | 3306 |
|
|
| Migration | Python 3 pipeline (legacy Access → staging → transforms) | — |
|
|
|
|
Monorepo managed with **pnpm workspaces**. Node **>= 20**.
|
|
|
|
---
|
|
|
|
## Repository layout
|
|
|
|
```
|
|
apps/
|
|
web/ Next.js frontend (@jorgecuadros/web)
|
|
api/ NestJS backend (@jorgecuadros/api)
|
|
scripts/seed-user.mjs idempotent admin seeder
|
|
packages/
|
|
database/ Prisma schema + generated client (@jorgecuadros/database)
|
|
prisma/schema.prisma
|
|
migration/ One-off Python ETL from the legacy Access DB (run_all.py)
|
|
docker/ Dockerfiles for api + web
|
|
docker-compose.yml mysql + api + web
|
|
.env.example copy to .env
|
|
```
|
|
|
|
API feature modules: `auth`, `users`, `customers`, `policies`, `properties`,
|
|
`billing`, `bank`, `reports`, `notifications`, `renewals`, `mail`, `statements`,
|
|
`policy-ocr`, `ocr`, `storage`, `settings`, `ops`.
|
|
|
|
Web routes: `/inicio`, `/clientes`, `/polizas`, `/servicios`, `/estado-cuenta`,
|
|
`/banco` (chequera), `/recibos` (OCR capture), `/notificaciones` (mass email +
|
|
renewal avisos; `/renovaciones` is an alias onto its Pólizas tab), `/reportes`,
|
|
`/catalogos`, `/operaciones` (DB ingest/backup, ADMIN), `/usuarios`, `/login`.
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
- Node.js >= 20 and **pnpm** (`npm i -g pnpm`)
|
|
- Docker (for MySQL, or bring your own MySQL 8)
|
|
- Python 3 — only if you run the legacy data migration
|
|
|
|
---
|
|
|
|
## Run it locally (development)
|
|
|
|
### 1. Install
|
|
|
|
```bash
|
|
pnpm install
|
|
```
|
|
|
|
pnpm blocks postinstall build scripts by default; the trusted ones
|
|
(`argon2`, `prisma`, `@prisma/client`, `@prisma/engines`, `@nestjs/core`) are
|
|
allowlisted in `pnpm-workspace.yaml`, so the native builds run automatically.
|
|
|
|
### 2. Configure environment
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Then edit `.env`. For the Docker MySQL below the defaults already line up;
|
|
just set a real `SESSION_SECRET`:
|
|
|
|
```env
|
|
DATABASE_URL=mysql://jorgecuadros:jorgecuadros@localhost:3306/jorgecuadros
|
|
SESSION_SECRET=<any long random string>
|
|
WEB_ORIGIN=http://localhost:3000
|
|
NEXT_PUBLIC_API_ORIGIN=http://localhost:3001
|
|
```
|
|
|
|
The API loads `DATABASE_URL`, `SESSION_SECRET`, `WEB_ORIGIN`, and optional
|
|
`PORT` (default `3001`). The web app only needs `NEXT_PUBLIC_API_ORIGIN`.
|
|
|
|
### 3. Start MySQL
|
|
|
|
```bash
|
|
docker compose up -d mysql
|
|
```
|
|
|
|
(Or point `DATABASE_URL` at an existing MySQL 8 instance.)
|
|
|
|
### 4. Create the schema + generate the Prisma client
|
|
|
|
The schema is managed with `prisma db push` (no migration history committed):
|
|
|
|
```bash
|
|
pnpm --filter @jorgecuadros/database exec prisma db push
|
|
pnpm --filter @jorgecuadros/database generate
|
|
```
|
|
|
|
### 5. Seed a sign-in user
|
|
|
|
```bash
|
|
node apps/api/scripts/seed-user.mjs
|
|
```
|
|
|
|
Idempotent (upsert by email). Defaults — override with `SEED_EMAIL`,
|
|
`SEED_PASSWORD`, `SEED_NAME`:
|
|
|
|
- email: `admin@jorgecuadros.local`
|
|
- password: `ChangeMe!2026`
|
|
- role: `ADMIN`
|
|
|
|
### 6. Run the apps (two terminals)
|
|
|
|
```bash
|
|
# API → http://localhost:3001
|
|
pnpm --filter @jorgecuadros/api start:dev
|
|
|
|
# Web → http://localhost:3000
|
|
pnpm --filter @jorgecuadros/web dev
|
|
```
|
|
|
|
Root shortcuts also exist: `pnpm dev:api`, `pnpm dev:web`.
|
|
|
|
### 7. Log in
|
|
|
|
Open http://localhost:3000, sign in with the seeded credentials.
|
|
Sessions are cookie-based and last 8 hours.
|
|
|
|
---
|
|
|
|
## Run it with Docker (full stack)
|
|
|
|
Builds MySQL + API + web from `docker-compose.yml`:
|
|
|
|
```bash
|
|
export SESSION_SECRET=$(openssl rand -hex 32)
|
|
docker compose up --build
|
|
```
|
|
|
|
Web on http://localhost:3000, API on http://localhost:3001. `SESSION_SECRET`
|
|
is required (compose fails without it). After first boot, push the schema and
|
|
seed a user against the container DB:
|
|
|
|
```bash
|
|
docker compose exec api node apps/api/scripts/seed-user.mjs
|
|
```
|
|
|
|
---
|
|
|
|
## Common commands
|
|
|
|
| Task | Command |
|
|
| ------------------------ | -------------------------------------------------------------- |
|
|
| Install | `pnpm install` |
|
|
| Dev — API | `pnpm dev:api` |
|
|
| Dev — Web | `pnpm dev:web` |
|
|
| Build all | `pnpm build` |
|
|
| Generate Prisma client | `pnpm prisma:generate` |
|
|
| Push schema (dev) | `pnpm --filter @jorgecuadros/database exec prisma db push` |
|
|
| Prisma Studio | `pnpm prisma:studio` |
|
|
| API tests | `pnpm --filter @jorgecuadros/api test` |
|
|
| Lint (web / api) | `pnpm --filter @jorgecuadros/web lint` · `... /api lint` |
|
|
| Seed admin user | `node apps/api/scripts/seed-user.mjs` |
|
|
|
|
---
|
|
|
|
## Auth & roles
|
|
|
|
Session auth via Passport local strategy; passwords hashed with Argon2 (no
|
|
plaintext, unlike the legacy app). Roles gate the UI and API — e.g. managing
|
|
`/catalogos` and `/usuarios` requires the appropriate ability (`ADMIN` /
|
|
`MANAGER`). New users are created by an admin in `/usuarios`; the first admin
|
|
comes from the seed script above.
|
|
|
|
---
|
|
|
|
## Legacy data migration (optional)
|
|
|
|
`migration/` holds the one-off Python ETL that lifts data out of the old
|
|
Microsoft Access database into MySQL.
|
|
|
|
```bash
|
|
pip install -r migration/requirements.txt
|
|
python migration/run_all.py
|
|
```
|
|
|
|
> ⚠️ `run_all.py` truncates and reloads **all** downstream tables. Never run an
|
|
> individual `transform_*.py` in isolation — it orphans dependent tables. See
|
|
> `migration/RECONCILIATION.md` for details.
|
|
|
|
---
|
|
|
|
## Scheduled jobs
|
|
|
|
The API runs two automatic email sweeps. Neither cadence is in the source:
|
|
both are stored in `app_settings` and edited at `/notificaciones` →
|
|
"Programación de envíos" (ADMIN, `setting:manage`), taking effect immediately
|
|
without a restart. Shipped defaults:
|
|
|
|
| Job | Default | What it does |
|
|
| --- | ------- | ------------ |
|
|
| Pólizas | **on**, 06:00 daily (America/Tijuana) | Renewal avisos at 30/15 days before expiry and 7 days after. |
|
|
| Servicios | **off** | All four mass-email jobs in order, same as "Ejecutar todos". |
|
|
|
|
A scheduled run never uses the UI's send flags — in particular it ignores
|
|
`debug`, so a forgotten test toggle cannot silently stop customer mail. Full
|
|
detail in [`docs/MASS_EMAIL_NOTIFICATIONS.md`](docs/MASS_EMAIL_NOTIFICATIONS.md).
|
|
|
|
Sending needs `SES_*` in the environment. Without it the API still boots and
|
|
logs mail to stdout in dev; in production every send fails loudly and is
|
|
recorded as `FAILED` rather than quietly going nowhere.
|
|
|
|
---
|
|
|
|
## Production notes
|
|
|
|
- Use `pnpm --filter @jorgecuadros/database exec prisma migrate deploy` if/when
|
|
a committed migration history is adopted; today dev uses `db push`.
|
|
- Set a strong `SESSION_SECRET` and a locked-down `DATABASE_URL`.
|
|
- The API expects `WEB_ORIGIN` to match the browser origin for session cookies.
|
|
- Documents are stored in MinIO in the deployed environment (see `RESUME.md`).
|