Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/LizandroCanul/back_sdo/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Yucatan Public Works API uses TypeORM as its ORM layer with PostgreSQL as the database backend. Geographic data is handled using PostGIS extensions for spatial operations.

Key Entities

Obra (Public Work)

The central entity representing a public works project.
@Entity('obras')
export class Obra {
  @PrimaryGeneratedColumn('increment')
  id: number;

  @Column({ name: 'numero_obra', type: 'int' })
  numeroObra: number; // Auto-calculated annual consecutive number

  @Column({ name: 'clave_unica', length: 50, unique: true })
  claveUnica: string; // Unique identifier

  @Column({ length: 255 })
  nombre: string;

  @Column({ type: 'text', nullable: true })
  descripcion: string;

  @Column({ type: 'decimal', precision: 18, scale: 2, default: 0 })
  monto: number; // Budget amount

  // Foreign key relationships
  @ManyToOne(() => EjercicioFiscal, { nullable: false, eager: true })
  ejercicioFiscal: EjercicioFiscal;

  @ManyToOne(() => Dependencia, { nullable: false, eager: true })
  dependencia: Dependencia;

  @ManyToOne(() => Municipio, { nullable: false, eager: true })
  municipio: Municipio;

  @ManyToOne(() => TipoProyecto, { nullable: true, eager: true })
  tipoProyecto: TipoProyecto;

  @ManyToOne(() => EstatusObra, { nullable: false, eager: true })
  estatusObra: EstatusObra;

  // Child relationships
  @OneToMany(() => ObraUbicacion, (ubicacion) => ubicacion.obra, {
    cascade: true
  })
  ubicaciones: ObraUbicacion[];
}
Key Fields:
  • numeroObra: Auto-generated annual consecutive number
  • claveUnica: Unique project identifier (max 50 chars)
  • monto: Budget with 2 decimal precision (up to 18 digits)
  • Uses eager: true for automatic relation loading

ObraUbicacion (Location)

Handles geographic data for public works using PostGIS.
export enum TipoGeometria {
  PUNTO = 'PUNTO',
  RUTA = 'RUTA',
  POLIGONO = 'POLIGONO',
}

@Entity('obra_ubicaciones')
export class ObraUbicacion {
  @PrimaryGeneratedColumn('increment')
  id: number;

  @ManyToOne(() => Municipio, { nullable: false })
  municipio: Municipio;

  @Column({ length: 255 })
  direccion: string;

  @Column({ name: 'localidad_referencia', length: 150, nullable: true })
  localidadReferencia: string;

  @Column({ name: 'referencia_lugar', length: 150, nullable: true })
  referenciaLugar: string;

  @Column({ type: 'enum', enum: TipoGeometria, default: TipoGeometria.PUNTO })
  tipoGeometria: TipoGeometria;

  @Column({ name: 'geometria_json', type: 'json' })
  geometriaJson: any; // GeoJSON data

  @Column({ default: 0 })
  orden: number;

  @ManyToOne(() => Obra, (obra) => obra.ubicaciones, { onDelete: 'CASCADE' })
  obra: Obra;
}
Each location can be a PUNTO (point), RUTA (line), or POLIGONO (polygon). Geographic data is stored as JSON for flexibility.

User

User authentication and authorization entity.
@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string; // UUID for security

  @Column('text')
  nombreCompleto: string;

  @Column('text', { unique: true })
  email: string;

  @Column('text', { select: false })
  password: string; // Never returned in queries

  @Column('text', { default: 'user' })
  roles: string; // 'admin' | 'user'

  @Column('bool', { default: true })
  isActive: boolean;

  @Column('bool', { default: true })
  mustChangePassword: boolean;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @OneToMany(() => UserFavoriteObra, (fav) => fav.user, { cascade: true })
  favoritasObras: UserFavoriteObra[];
}
The password field uses select: false to prevent accidental exposure. You must explicitly select it when needed.

Municipio (Municipality)

Municipality entity with PostGIS spatial indexing.
import type { Point } from 'geojson';

@Entity('municipios')
export class Municipio {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ type: 'text', unique: true })
  nombre: string;

  // PostGIS geometry column
  @Index({ spatial: true })
  @Column({
    type: 'geometry',
    spatialFeatureType: 'Point',
    srid: 4326, // WGS84 coordinate system
    nullable: true,
  })
  ubicacion: Point;

  @Column({ type: 'boolean', default: true })
  activo: boolean; // Soft delete

  @CreateDateColumn({ select: false })
  created_at: Date;

  @UpdateDateColumn({ select: false })
  updated_at: Date;
}
PostGIS Integration:
  • Uses geometry type with spatial indexing
  • srid: 4326 = WGS84 (standard GPS coordinates)
  • Supports spatial queries and distance calculations

Catalog Entities

Simple catalog tables for dropdowns and classifications.
@Entity('dependencias')
export class Dependencia {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ type: 'text', unique: true })
  nombre: string;

  @Column({ type: 'text', nullable: true })
  siglas: string;

  @Column({ type: 'boolean', default: true })
  activo: boolean;
}

Entity Relationships

1

Obra → Municipio

Many obras belong to one municipio (nullable: false, eager: true)
2

Obra → Dependencia

Many obras belong to one dependencia (nullable: false, eager: true)
3

Obra → EjercicioFiscal

Many obras belong to one fiscal year (nullable: false, eager: true)
4

Obra → TipoProyecto

Many obras can have one project type (nullable: true, eager: true)
5

Obra → EstatusObra

Many obras have one status (nullable: false, eager: true)
6

Obra ↔ ObraUbicacion

One obra has many ubicaciones (cascade: true, onDelete: CASCADE)
7

User ↔ Obra (Favorites)

Many-to-many through UserFavoriteObra junction table

UserFavoriteObra Junction Table

@Entity('user_favorite_obra')
@Unique(['user', 'obra']) // Prevents duplicate favorites
export class UserFavoriteObra {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => User, (user) => user.favoritasObras, { onDelete: 'CASCADE' })
  user: User;

  @Column({ name: 'user_id', type: 'uuid' })
  userId: string;

  @ManyToOne(() => Obra, (obra) => obra.usuariosQueLoMarcanFavorita, { onDelete: 'CASCADE' })
  obra: Obra;

  @Column({ name: 'obra_id' })
  obraId: number;

  @CreateDateColumn()
  createdAt: Date;
}

Foreign Key Conventions

The project follows these TypeORM patterns:

Relation Column

Stores the full related entity
@ManyToOne(() => Municipio)
municipio: Municipio;

ID Column

Stores just the foreign key ID
@Column({ name: 'municipio_id' })
municipioId: number;

Eager Loading

Automatically loads relations
@ManyToOne(() => Municipio, { eager: true })

Cascade Operations

Auto-saves/deletes children
@OneToMany(..., { cascade: true })

Soft Deletes

Catalog entities use the activo boolean field for soft deletion:
@Column({ type: 'boolean', default: true })
activo: boolean;
This preserves historical data while hiding inactive records from normal queries.

Next Steps

Validation

Learn about DTO validation patterns

Error Handling

Understand error responses

Build docs developers (and LLMs) love