Skip to main content

XSD Code Generation Strategy for FEL

🎯 Overview​

The FELgt repository uses XSD-to-C# code generation to automatically create strongly-typed classes from SAT's XML schemas. This is a brilliant approach that we should adopt for TypeScript.


πŸ’‘ Why Code Generation is Powerful​

Benefits​

  1. βœ… 100% Schema Compliance

    • Generated code matches XSD exactly
    • No manual interpretation errors
    • Guaranteed structure correctness
  2. βœ… Type Safety

    • TypeScript interfaces generated from XSD
    • Compile-time validation
    • IDE autocomplete support
  3. βœ… Auto-Updates

    • When SAT updates XSD, regenerate code
    • No manual updates needed
    • Always in sync with official schema
  4. βœ… Reduced Errors

    • No typos in XML element names
    • Correct namespace declarations
    • Proper data types
  5. βœ… Documentation

    • Generated types serve as documentation
    • See all required/optional fields
    • Understand relationships

πŸ”§ Available Tools for TypeScript​

Package: xsd2ts or xsd-typescript-generator

Installation:

npm install --save-dev xsd-typescript-generator
# or
npm install --save-dev xsd2ts

Usage:

xsd2ts -i docs/fel/xsd/*.xsd -o apps/backend/src/fel/generated/

Output:

  • TypeScript interfaces
  • Type definitions
  • Namespace mappings

Option 2: quicktype​

Package: quicktype

Installation:

npm install -g quicktype

Usage:

quicktype --src docs/fel/xsd/fact.xsd --lang typescript --out apps/backend/src/fel/generated/fact.types.ts

Features:

  • Supports XSD
  • Generates TypeScript types
  • Can generate JSON Schema first, then TypeScript

Create: packages/backend/scripts/src/fel/generate-fel-types.ts

Approach:

  1. Parse XSD files
  2. Generate TypeScript interfaces
  3. Generate XML builder helpers
  4. Generate validation functions

πŸ“‹ Implementation Strategy​

Phase 1: Download XSD Files​

# Create directory
mkdir -p docs/fel/xsd

# Download SAT XSD files
cd docs/fel/xsd
wget -r --no-parent --accept=xsd https://cat.desa.sat.gob.gt/xsd/alfa/

# Download XML Signature schema
wget https://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd

Phase 2: Generate TypeScript Types​

Script: packages/backend/scripts/src/fel/generate-fel-types.ts

import { parseXSD } from 'xsd-parser';
import { generateTypeScript } from 'xsd-to-typescript';
import fs from 'fs';
import path from 'path';

async function generateFelTypes() {
const xsdDir = path.join(__dirname, '../../../docs/fel/xsd');
const outputDir = path.join(__dirname, '../../../apps/backend/src/fel/generated');

// Parse XSD files
const factXsd = await parseXSD(path.join(xsdDir, 'fact.xsd'));
const ncreXsd = await parseXSD(path.join(xsdDir, 'ncre.xsd'));
const ndebXsd = await parseXSD(path.join(xsdDir, 'ndeb.xsd'));
const anulacionXsd = await parseXSD(path.join(xsdDir, 'anulacion.xsd'));

// Generate TypeScript interfaces
const factTypes = generateTypeScript(factXsd, 'FactDocument');
const ncreTypes = generateTypeScript(ncreXsd, 'CreditNoteDocument');
const ndebTypes = generateTypeScript(ndebXsd, 'DebitNoteDocument');
const anulacionTypes = generateTypeScript(anulacionXsd, 'CancellationDocument');

// Write to files
fs.writeFileSync(path.join(outputDir, 'fact.types.ts'), factTypes);
fs.writeFileSync(path.join(outputDir, 'ncre.types.ts'), ncreTypes);
fs.writeFileSync(path.join(outputDir, 'ndeb.types.ts'), ndebTypes);
fs.writeFileSync(path.join(outputDir, 'anulacion.types.ts'), anulacionTypes);

console.log('βœ… FEL types generated successfully!');
}

Phase 3: Add to Package Scripts​

File: packages/backend/scripts/package.json

{
"scripts": {
"generate:fel-types": "tsx src/fel/generate-fel-types.ts"
}
}

Phase 4: Use Generated Types​

Update: apps/backend/src/fel/domain/fel.interface.ts

// Import generated types
import type { FactDocument } from '../generated/fact.types';
import type { CreditNoteDocument } from '../generated/ncre.types';
import type { DebitNoteDocument } from '../generated/ndeb.types';
import type { CancellationDocument } from '../generated/anulacion.types';

// Union type for all FEL documents
export type FelDocument =
| FactDocument
| CreditNoteDocument
| DebitNoteDocument
| CancellationDocument;

Why Hybrid?​

Full code generation can be complex and may not match your current patterns. A hybrid approach gives you:

  1. βœ… Generated types for validation
  2. βœ… Manual XML builders (your current approach)
  3. βœ… Best of both worlds

Strategy​

  1. Generate Types from XSD

    • Use for validation
    • Use for type checking
    • Reference for XML structure
  2. Keep Manual XML Builders

    • Your current XmlConversionService approach
    • More control over structure
    • Easier to customize
  3. Use Types for Validation

    • Validate JSON before XML conversion
    • Type-check XML builders
    • Ensure compliance

Example​

// Generated from XSD
interface FactDocument {
DatosGenerales: {
Tipo: 'FACT';
FechaHoraEmision: string;
CodigoMoneda: string;
};
Emisor: { /* ... */ };
Receptor: { /* ... */ };
Items: { /* ... */ };
// ... all fields from XSD
}

// Your current interface (simplified)
interface Invoice {
generalData: { type: string; dateTimeIssue: string; currencyCode: string };
issuerData: { /* ... */ };
receiverData: { /* ... */ };
items: InvoiceItem[];
// ... your simplified structure
}

// Mapper function
function mapInvoiceToFactDocument(invoice: Invoice): FactDocument {
return {
DatosGenerales: {
Tipo: 'FACT',
FechaHoraEmision: invoice.generalData.dateTimeIssue,
CodigoMoneda: invoice.generalData.currencyCode,
},
// ... map other fields
};
}

// Validation
function validateFactDocument(doc: FactDocument): boolean {
// Use XSD-generated types for validation
// Check required fields, data types, etc.
return true;
}

πŸ”„ Workflow Integration​

Development Workflow​

# 1. Download latest XSD files (when SAT updates)
pnpm fel:download-xsd

# 2. Regenerate TypeScript types
pnpm generate:fel-types

# 3. TypeScript compiler will catch any breaking changes
pnpm build

# 4. Update XML builders if needed
# (Your XmlConversionService)

CI/CD Integration​

# .github/workflows/fel-types.yml
name: Update FEL Types

on:
schedule:
- cron: '0 0 * * 1' # Weekly check
workflow_dispatch:

jobs:
update-types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download XSD files
run: pnpm fel:download-xsd
- name: Generate types
run: pnpm generate:fel-types
- name: Check for changes
run: |
git diff --exit-code || {
echo "FEL types have changed!"
git add apps/backend/src/fel/generated/
git commit -m "chore: update FEL types from XSD"
git push
}

πŸ“Š Comparison: Manual vs Generated​

Manual Approach (Current)​

Pros:

  • βœ… Full control
  • βœ… Simplified interfaces
  • βœ… Easy to customize
  • βœ… Matches your patterns

Cons:

  • ❌ Manual updates when XSD changes
  • ❌ Risk of missing fields
  • ❌ No automatic validation
  • ❌ Potential for errors

Generated Approach​

Pros:

  • βœ… Always in sync with XSD
  • βœ… Type safety
  • βœ… Auto-updates
  • βœ… No manual errors

Cons:

  • ❌ More complex types
  • ❌ May not match your patterns
  • ❌ Requires XSD parsing
  • ❌ Generated code can be verbose

Hybrid Approach (Best)​

Pros:

  • βœ… Best of both worlds
  • βœ… Types for validation
  • βœ… Manual builders for control
  • βœ… Reference for structure

Cons:

  • ⚠️ Slight complexity
  • ⚠️ Need to maintain mapping

Step 1: Download XSD Files​

mkdir -p docs/fel/xsd
cd docs/fel/xsd
wget -r --no-parent --accept=xsd https://cat.desa.sat.gob.gt/xsd/alfa/

Step 2: Create Generation Script​

File: packages/backend/scripts/src/fel/generate-fel-types.ts

Use a library like:

  • xsd-typescript-generator
  • quicktype
  • Or custom parser

Step 3: Generate Types​

pnpm generate:fel-types

Step 4: Use Types for Reference​

  • Keep your current Invoice interface
  • Use generated types as reference
  • Validate against generated types
  • Update XML builders based on generated types

Step 5: Optional Validation​

import type { FactDocument } from '../generated/fact.types';

function validateInvoice(invoice: Invoice): void {
// Convert to FactDocument structure
const factDoc = mapInvoiceToFactDocument(invoice);

// Validate against XSD-generated types
// (would need runtime validation library)
}

πŸ’‘ My Recommendation​

Use a Hybrid Approach:

  1. Download XSD files - Store in docs/fel/xsd/
  2. Generate TypeScript types - For reference and validation
  3. Keep your current XML builders - They work well
  4. Use generated types as reference - When extending for NCRE/NDEB/ANULACION
  5. Optional: Add runtime validation - Using generated types

Why?

  • Your current approach is clean and works
  • Generated types ensure you don't miss fields
  • Hybrid gives you flexibility
  • Less risk than full code generation

πŸ“ Next Steps​

  1. Research TypeScript XSD generators:

    • Test xsd-typescript-generator
    • Test quicktype
    • Evaluate output quality
  2. Download XSD files:

    mkdir -p docs/fel/xsd
    cd docs/fel/xsd
    wget -r --no-parent --accept=xsd https://cat.desa.sat.gob.gt/xsd/alfa/
  3. Create generation script:

    • Start simple
    • Generate basic types
    • Iterate based on results
  4. Integrate into workflow:

    • Add to package.json scripts
    • Document in README
    • Consider CI/CD integration

πŸ”— References​


Last Updated: 2025-12-18
Status: Strategy Document - Ready for Implementation Decision