# 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` (+ `/polizas/captura`, policy PDF OCR capture), `/servicios`, `/estado-cuenta`, `/banco` (chequera), `/recibos` (utility statement 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`. Two OCR intakes share one `OcrProvider` seam (`src/ocr/`, Tesseract today): utility statements → ledger rows ([`docs/RECEIPT_CAPTURE_SPEC.md`](docs/RECEIPT_CAPTURE_SPEC.md) §2) and carrier policy PDFs → `Policy` rows ([`docs/POLICY_OCR.md`](docs/POLICY_OCR.md)). Both need `tesseract-ocr`, `tesseract-ocr-data-spa`, `poppler-utils` and object storage; each reports its own availability and disables only itself if either is missing. --- ## 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= 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`).