- Recupera el trabajo revertido (doble propuesta, retainer, niveles, API Python) desde el stash de GitHub Desktop - Dockerfile multi-stage para Next.js (migraciones automáticas al arrancar, seed opcional via RUN_SEED) - docker-compose.coolify.yml: postgres + web + api con healthchecks y SERVICE_FQDN_* - Endpoint público /api/health (verifica BD) para healthchecks - seed.ts parametrizado: conexión DB_* y credenciales SEED_* por entorno (sin passwords hardcodeadas) - .env.example, .dockerignore, uvicorn con --proxy-headers - Limpieza: .xlsx y __pycache__ fuera del repo Co-Authored-By: Claude Fable 5 <[email protected]>
7.7 KiB
AGENTS.md — Cotizador E3
Quick Reference
| Command | Purpose |
|---|---|
npm run dev |
Start Next.js dev server on port 3000 |
npm run build |
Production build (runs TypeScript check) |
npm run lint |
ESLint (flat config, eslint-config-next) |
npx tsx prisma/seed.ts |
Run seed (upserts all data, idempotent) |
npx prisma migrate dev |
Create/apply migration |
npx prisma generate |
Regenerate Prisma client |
npx prisma studio |
Prisma Studio GUI |
Windows environment. Use start.bat / stop.bat to manage Docker PostgreSQL + Next.js together. PowerShell is the shell. Paths with brackets (e.g. [id]) require -LiteralPath in PowerShell, not -Path.
Two backends, one database. The Next.js app (src/, port 3000) and a standalone Python FastAPI service (api/, port 8000) both talk to the same PostgreSQL DB. Prisma owns the schema/migrations; the Python API reads/writes the same tables independently. docker-compose.yml runs postgres + the api service; Next.js is run separately via npm run dev / start.bat.
Prisma 7 — Critical Gotchas
- Prisma client is NOT at
@prisma/client. It's generated tosrc/generated/prisma/and imported as@/generated/prisma/client. - Uses
PrismaPgdriver adapter (from@prisma/adapter-pg) with individual connection params (host/port/user/password/database), NOT a connection string. TheconnectionStringapproach causes SCRAM auth errors with postgres:postgres. prisma.config.tshas noseedproperty (unsupported by PrismaConfig type). Run seed manually withnpx tsx prisma/seed.ts.- After changing
schema.prisma: runnpx prisma migrate dev, thennpx prisma generate. - The
globalForPrismasingleton pattern insrc/lib/db.tsprevents hot-reload connection leaks in dev.
PDFKit — Server-Side PDF
pdfkitis excluded from Next.js bundling viaserverExternalPackagesinnext.config.ts. Without this, font metric files (.afm) can't be resolved at runtime.- Footer auto-page-break bug:
doc.text()aty > page.height - margins.bottomtriggers pdfkit's automatic page addition. To write footers in the bottom margin, temporarily setdoc.page.margins.bottom = 0, write, then restore. UsebufferPages: true+switchToPage()to add footers after all content. - Returns
Promise<Buffer>— convert toUint8ArrayforNextResponsebody.
Next.js 16 Conventions
- Async params: Dynamic route params are
Promise<{ id: string }>— mustawait paramsbefore use. - All pages are
force-dynamic— no static generation or ISR. - Tailwind CSS v4 — no
tailwind.config.*file. Config is inpostcss.config.mjs(using@tailwindcss/postcss) and CSS custom properties via@theme inlineinsrc/app/globals.css.
Data Model Notes
- Configuracion is a key-value store (
clave/valor), not a traditional settings model. New config keys:color_primario,color_secundario,logo_base64(branding for PDF export). - Cascading deletes: Cotizacion → ServicioCotizado, PlanBucefaloCotizacion use
onDelete: Cascade. - Cotización number format:
UJ{YY}{MM}{AsesorInitials}{seq}(e.g.UJ2605AG001) — seegenerarNumeroCotizacioninsrc/lib/calculators.ts. Sequence is 3 digits, zero-padded. - Vigencia = 15 business days from fecha (excludes Sat/Sun) —
calcularVigencia. - IVA_RATE = 0.16 (16%).
- Bucéfalo CRM plan prices (in
calculators.ts, NOT the DB): basico=$1,000, estandar=$3,500, premium=$4,500, empresarial=$7,500 (monthly). - Financing lives in the
FinanciamientoPlantable (3/6/9/12 months). Formula:comisionTotal = monto × comision%,pagoMensual = (monto + comisionTotal) × (1 + tasa) / meses, then add 16% IVA. Both backends must keep this formula identical.
Auth
- JWT sessions (
src/lib/auth.ts) signed withjose(HS256, 7-day expiry), stored in thecotizador-sessionhttpOnly cookie.JWT_SECRETenv var is required (throws at startup if missing). src/middleware.tsgates everything exceptPUBLIC_PATHS(/login,/api/auth/login,/api/auth/logout,/api/health). Unauthenticated API calls → 401 JSON; pages → redirect to/login. On success it injectsx-user-id/x-user-rolerequest headers for downstream handlers.- Passwords hashed with
bcryptjs. Roles:asesor(default), and others stored as free-form strings.
Deployment (Coolify)
- Production stack:
docker-compose.coolify.yml(postgres + web + api). In Coolify set Docker Compose Location to/docker-compose.coolify.yml. Local dev keeps usingdocker-compose.yml+start.bat. - The
webcontainer runsprisma migrate deployon every start, and the seed only whenRUN_SEED=true. Seed users/passwords come fromSEED_*env vars (see.env.example) —prisma/seed.tsnever overwrites an existing password unless the env var is set. DATABASE_URLis only used by the Prisma CLI (migrations); the apps use individualDB_*vars. Both must point to the same DB.- Healthchecks: web
GET /api/health(public in middleware, pings DB), apiGET /health. - Root
Dockerfilebuilds Next.js withJWT_SECRET=placeholder-build-only(auth.ts throws at import without it; all pages are force-dynamic so nothing gets baked). *.xlsxis gitignored (business files must never be committed).
Architecture
src/
app/
(app)/ # Authed route group: dashboard, cotizaciones, clientes, catalogo, configuracion (has its own layout.tsx + Sidebar)
api/ # Next.js route handlers (REST): auth, catalogo, categorias, cotizaciones, configuracion, paquetes, export, import
login/ # Public login page
components/ # CotizacionForm, ExportButtons, EstadoBadge, layout/Sidebar
lib/ # auth, db, store, calculators, pdf-generator, schemas, config-helpers
generated/prisma/ # Prisma client output (gitignored)
prisma/
schema.prisma # 13 models (User, Cliente, Cotizacion, Categoria, Paquete, FasePaquete,
# ServicioCatalogo, ServicioPaquete, ServicioCotizado, PlanBucefaloCotizacion,
# Configuracion, Bono, FinanciamientoPlan)
seed.ts # All catalog data (services, categorias, bonos, planes, config) — idempotent upserts
migrations/ # 3 migrations
api/ # Standalone Python FastAPI + MCP server (see below)
- Zustand store (
src/lib/store.ts) holds the cotización draft. Used by both the new (cotizaciones/nueva) and edit (cotizaciones/[id]/editar) pages, both of which renderCotizacionForm.tsx. - ExportButtons.tsx has 4 variants:
ExportExcelButtonSaved/ExportPDFButtonSaved(GET by ID) andExportExcelButtonDraft/ExportPDFButtonDraft(POST with body).
Python API (api/) — Optional Second Backend
- FastAPI app (
api/main.py) exposing the same domain as REST, plus an MCP server at/mcpfor AI agents (n8n, Claude, ChatGPT). Tools/resources defined inapi/app/mcp/. - Auth: API key (
X-API-KeyorAuthorization: Bearer) for agents; JWT for human login. Routers inapi/app/routers/, business logic inapi/app/services/(its owncalculators.py,pdf_generator.py,excel_generator.py— mirror the TS versions). - Run:
cd api && pip install -r requirements.txt && uvicorn main:app --reload --port 8000. Swagger at/docs. Full endpoint reference inapi/COTIZADOR_API_SKILL.md. - It reads
DB_*,API_KEY,JWT_SECRETenv vars (same DB as Prisma).
Domain
Quotation system for Consultoría E3 (digital marketing agency in Querétaro, MX). Services organized in 4 phases: Fase 0 (Auditorías), Fase 1 (Setup/Infra), Fase 2 (Publicidad/Manejo), Fase 3 (Contenido/SEO). Two payment types: unico (one-time) and mensual (recurring). Optional Bucéfalo CRM plans and Openpay financing.