Files
jorgecuadros-platform/apps/api/src/reports/company.ts
T
rmancinas 8802f08d4f
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m35s
Build and Push Images / Build jorgecuadros-api (push) Successful in 2m11s
feat(reports): reports module + /inicio + edo-cuenta-datos prefill
- New reports backend (registry, service, controller, outputs, types)
  with catalog endpoint + slug/CSV/XLSX/PDF/print outputs.
- /reportes catalog + /reportes/[slug] runner; ReportRunner + ContextReports
  components wire pre-filtered links from domain pages.
- Fix: /reportes/[slug] now reads searchParams and forwards initialParams to
  ReportRunner so /reportes/edo-cuenta-datos?customerId=... auto-runs
  instead of dropping the id and forcing a manual customer search.
- /inicio landing page; root + login redirect to /inicio.
- Company header env vars + logo asset for PDF/print rendering.
- exceljs + pdfkit deps.
2026-07-23 23:20:41 -07:00

104 lines
3.4 KiB
TypeScript

/**
* Company info used on every report header (PDF + print). Read from
* the environment so the office can edit it without a code change —
* the .env.example file lists the keys; defaults below are placeholders
* the office should override for production.
*
* Single source of truth: the API renders the header. The web header
* (login + AppShell) still reads the static "Jorge Cuadros & Asociados"
* strings for now — those are visual brand, the API's COMPANY_INFO
* block is the legal/locator block on printed documents.
*/
import * as fs from "node:fs";
import * as path from "node:path";
export interface CompanyInfo {
name: string;
/** Street address — line 1. */
addressLine1: string;
/** Street address — line 2 (suite, floor, etc.). Optional. */
addressLine2: string;
/** "City, State, ZIP, Country" — single line. */
cityState: string;
phone: string;
email: string;
/** Mexican tax ID ("RFC"). Optional. */
taxId: string;
website: string;
/** Absolute path to the logo PNG. Null when missing — renderers fall
* back to a text mark. */
logoPath: string | null;
/** Logo buffer + intrinsic size, eagerly loaded so the PDF renderer
* doesn't do a sync read on every report. Null when no logo. */
logo: { buffer: Buffer; width: number; height: number } | null;
}
function envOr(key: string, fallback: string): string {
const v = process.env[key];
return v && v.trim() ? v : fallback;
}
function resolveLogoPath(): string | null {
const explicit = process.env.COMPANY_LOGO_PATH;
if (explicit) {
return fs.existsSync(explicit) ? explicit : null;
}
// Default: look in apps/api/assets/company_logo.png (copied from
// apps/web/public/images/company_logo.png — single canonical image
// kept in lock-step; see .env.example for the override path).
const candidates = [
path.resolve(__dirname, "..", "..", "assets", "company_logo.png"),
path.resolve(__dirname, "..", "..", "..", "web", "public", "images", "company_logo.png"),
];
for (const c of candidates) {
if (fs.existsSync(c)) return c;
}
return null;
}
let cached: CompanyInfo | null = null;
export function getCompanyInfo(): CompanyInfo {
if (cached) return cached;
const logoPath = resolveLogoPath();
let logo: CompanyInfo["logo"] = null;
if (logoPath) {
try {
const buf = fs.readFileSync(logoPath);
// Intrinsic PNG size: read IHDR (bytes 16-23 of the file).
// Width = BE uint32 at offset 16, height = BE uint32 at offset 20.
const w =
logoPath.endsWith(".png") && buf.length >= 24
? buf.readUInt32BE(16)
: 0;
const h =
logoPath.endsWith(".png") && buf.length >= 24
? buf.readUInt32BE(20)
: 0;
logo = { buffer: buf, width: w, height: h };
} catch {
logo = null;
}
}
cached = {
name: envOr("COMPANY_NAME", "Jorge Cuadros & Asociados"),
addressLine1: envOr(
"COMPANY_ADDRESS_LINE1",
"Av. Revolución 1234, Int. 5",
),
addressLine2: envOr("COMPANY_ADDRESS_LINE2", ""),
cityState: envOr(
"COMPANY_CITY_STATE",
"Tijuana, Baja California 22000, México",
),
phone: envOr("COMPANY_PHONE", "(664) 000-0000"),
email: envOr("COMPANY_EMAIL", "contacto@jorgecuadros.local"),
taxId: envOr("COMPANY_TAX_ID", ""),
website: envOr("COMPANY_WEBSITE", "jorgecuadros.local"),
logoPath,
logo,
};
return cached;
}