02 Desarrollo Backend

Actualizado: 17 de marzo de 2026

Ruta de Aprendizaje y Onboarding: Desarrollo Backend#

La arquitectura backend de la plataforma SoyDigital / Indotel es el núcleo responsable de procesar la lógica de negocio, reglas de validación en cascada, sincronización offline/online, y la provisión de datos para los simuladores educativos. El stack principal es Node.js, TypeScript, NestJS y Prisma/TypeORM sobre PostgreSQL.

Tiempo estimado de estudio: 40–60 horas

Prerrequisitos: Conocimientos básicos de programación. Si nunca has programado, completa primero un curso introductorio de JavaScript.


Nivel 1 (Junior) - Fundamentos — Desde Cero hasta la API#

Este nivel es para desarrolladores que vienen de otros lenguajes o que necesitan reforzar sus bases en el ecosistema backend web.


Clase 1.1: ¿Qué es una API y por qué existe?#

API (Application Programming Interface) es un contrato de comunicación entre software. En nuestro contexto:

Ctrl+Scroll=Zoom · Arrastrar=Mover

¿Por qué no consultar la base de datos directamente desde el teléfono?#

  1. Seguridad: La base de datos estaría expuesta a todo el mundo
  2. Validación: Cualquiera podría auto-asignarse notas perfectas
  3. Consistencia: Las reglas de negocio (anti-downgrade, tolerancia LAN, etc.) viven en un solo lugar
  4. Abstracción: Si cambias la base de datos, el frontend no se entera

El protocolo HTTP en 5 minutos#

HTTP (HyperText Transfer Protocol) es el "idioma" que hablan el navegador y el servidor:

ConceptoExplicaciónEjemplo
URLLa dirección del recursohttps://api.soydigital.gob.do/api/users/123
Método/VerboLa acción que quieres hacerGET, POST, PUT, PATCH, DELETE
HeadersMetadatos de la peticiónAuthorization: Bearer eyJ... (token de autenticación)
BodyLos datos que envías{ "cedula": "40200000000", "nombre": "Juan" }
Status CodeEl resultado del servidor200 OK, 201 Creado, 400 Error del cliente, 401 No autorizado, 404 No encontrado, 500 Error del servidor

Los métodos HTTP y cuándo usarlos:

MétodoAcciónEjemplo en SoyDigital¿Modifica datos?
GETObtener datosConsultar el progreso de un usuarioNo
POSTCrear algo nuevoRegistrar un nuevo usuario
PUTReemplazar completamenteActualizar todo el perfil
PATCHActualizar parcialmenteCambiar solo el email
DELETEEliminarBorrar un intento de diagnóstico

¿Qué es JSON?#

JSON (JavaScript Object Notation) es el formato estándar para intercambiar datos. Es texto plano legible tanto por humanos como por máquinas:

json
{
  "user_id": "abc-123",
  "full_name": "María García",
  "current_level": 2,
  "certificates": [
    { "level": 1, "issued_at": "2026-01-15" }
  ],
  "is_verified": true
}

Clase 1.2: JavaScript y TypeScript — El Lenguaje del Backend#

¿Por qué JavaScript en el servidor?#

Node.js permite ejecutar JavaScript fuera del navegador, en el servidor. Ventajas:

  • El mismo lenguaje en frontend y backend (los equipos comparten conocimiento)
  • Excelente para operaciones de I/O (lectura/escritura de base de datos, peticiones de red)
  • Ecosistema gigante de librerías (npm)

¿Qué es TypeScript?#

TypeScript es JavaScript con tipos estáticos. Esto significa que defines qué tipo de dato espera cada variable o función:

typescript
// JavaScript puro (sin tipos — cualquier cosa puede pasar)
function calcularEdad(nacimiento) {
  return 2026 - nacimiento; // ¿Y si nacimiento es "hola"?
}

// TypeScript (con tipos — errores se detectan ANTES de ejecutar)
function calcularEdad(nacimiento: number): number {
  return 2026 - nacimiento; // Si pasas "hola", TypeScript te grita 
}

¿Por qué usamos TypeScript en SoyDigital?

  • Previene errores tontos antes de que lleguen a producción
  • Los editores (VS Code) te muestran autocompletado inteligente
  • Cuando una API tiene 50+ endpoints, los tipos son documentación viviente

Conceptos TypeScript que verás constantemente#

ConceptoQué esEjemplo
InterfaceContrato de forma de un objetointerface User { name: string; age: number; }
EnumConjunto fijo de opcionesenum Role { ADMIN, USER, FACILITATOR }
GenericTipo paramétrico/reutilizableArray<User> — un array donde cada elemento es User
Optional (?)El campo puede no existiremail?: string — puede ser string o undefined
Type GuardVerificación de tipo en runtimeif (typeof x === 'string')

Clase 1.3: Node.js y npm — El Ecosistema#

➤ Profundización Técnica / Casos de Borde:

  • Bloqueo del Event Loop (CPU-Bound Task): El desarrollador Junior cometerá el error de pedir un archivo Base64 gigante y descifrarlo en el hilo principal de Node (JSON.parse de 50MB). Al ser Node Single-Threaded, todas las peticiones de los demás ciudadanos se congelarán por 2 segundos enteros esperando que ese cálculo termine. El código intensivo DEBE ejecutarse en Microservicios separados o worker_threads.

Node.js es el runtime (motor de ejecución) que permite correr JavaScript en el servidor.

npm (Node Package Manager) es el gestor de paquetes — descarga e instala librerías de terceros.

Archivos clave de cualquier proyecto Node.js#

ArchivoFunción
package.jsonManifiesto del proyecto: nombre, versión, dependencias, scripts
package-lock.jsonVersiones exactas de CADA dependencia (para reproducibilidad)
node_modules/Carpeta donde se descargan las librerías (NUNCA se sube a Git)
tsconfig.jsonConfiguración de TypeScript (qué tan estricto, target de compilación)
.envVariables de entorno (secretos, URLs, configuraciones)

Scripts que encontrarás en dominicana-api:#

bash
npm run build           # Compila TypeScript a JavaScript
npm run start           # Inicia el servidor compilado
npm run start:dev       # Inicia en modo desarrollo (hot reload)
npm run test            # Ejecuta tests unitarios con Jest
npm run db:docker:up    # Levanta PostgreSQL en Docker
npm run db:reset-and-migrate  # Resetea la DB y aplica migraciones
npm run db:seed         # Llena la DB con datos de prueba

Clase 1.4: NestJS — El Framework de Arquitectura#

➤ Profundización Técnica / Casos de Borde:

  • Memory Leaks por Scope: En NestJS por defecto, todos los servicios son Singletons (Instanciados 1 vez en RAM). Si un desarrollador cambia accidentalmente un servicio a Scope.REQUEST, Nest instanciará esa pesada clase de 10 megas en RAM por cada click (1000 clicks = 10 Gigas de RAM), evaporando el Mini-PC del Brigadista por completo (OOM).

A diferencia de Express (minimalista) o Fastify (rápido), NestJS obliga una arquitectura altamente estructurada inspirada en Angular. Esto es crucial en un proyecto del tamaño de SoyDigital donde hay 50+ endpoints y lógica compleja.

Los 4 pilares de NestJS#

Ctrl+Scroll=Zoom · Arrastrar=Mover
PilarDecoradorResponsabilidadAnalogía
Módulo@ModuleAgrupa Controller + Service + providers relacionadosUn departamento de la empresa
Controlador@ControllerRecibe HTTP, valida datos de entrada, llama al servicio, devuelve respuestaLa recepcionista
Servicio@InjectableContiene la lógica de negocio real. AQUÍ viven las reglasEl analista que hace el trabajo
Guard@UseGuardsDecide si una petición tiene permiso de pasarEl guardia de seguridad

Ejemplo real simplificado del proyecto:#

typescript
// users.controller.ts (Controlador — solo enruta)
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':id')
  @UseGuards(AuthGuard)  // ← Solo usuarios autenticados
  async getUser(@Param('id') id: string) {
    return this.usersService.findById(id);
  }
}

// users.service.ts (Servicio — la lógica real)
@Injectable()
export class UsersService {
  constructor(private readonly userRepository: UserRepository) {}

  async findById(id: string): Promise<User> {
    const user = await this.userRepository.findOne(id);
    if (!user) throw new NotFoundException('Usuario no encontrado');
    return user;
  }
}

Inyección de Dependencias (DI)#

NestJS instancia las clases automáticamente. No haces new UsersService() — NestJS lo hace por ti y lo "inyecta" donde se necesite. Beneficio: testeable, desacoplado, reutilizable.


Clase 1.5: Validaciones y DTOs#

DTO (Data Transfer Object) = el "contrato" de qué datos acepta cada endpoint.

typescript
// create-user.dto.ts
export class CreateUserDto {
  @IsString()
  @IsNotEmpty()
  full_name: string;

  @IsString()
  @Length(11, 11, { message: 'La cédula debe tener 11 dígitos' })
  cedula: string;

  @IsDateString()
  birth_date: string;

  @IsEnum(Gender)
  gender: Gender;
}

¿Qué pasa si el frontend envía datos incorrectos? El sistema de Pipes de NestJS intercepta ANTES de que el controlador procese la petición:

Ctrl+Scroll=Zoom · Arrastrar=Mover

Nivel 2 (Semi-Senior) - Base de Datos y ORM#


Clase 2.1: ¿Qué es una Base de Datos Relacional?#

Una base de datos relacional organiza la información en tablas (como hojas de Excel) con relaciones entre ellas.

PostgreSQL es nuestra base de datos. Es de código abierto, potente y confiable.

Ejemplo simplificado de nuestras tablas:#

Ctrl+Scroll=Zoom · Arrastrar=Mover

Relaciones clave:

  • Un usuario pertenece a un nivel → users.level_id apunta a levels.id
  • Un usuario tiene muchos registros de progreso → progress.user_id apunta a users.id

Clase 2.2: SQL — El Lenguaje de la Base de Datos#

➤ Profundización Técnica / Casos de Borde:

  • N+1 Problems & Missing Indexes: Hacer un loop .map() en el backend que ejecute SELECT cedula FROM users generará 10,000 conexiones separadas arrodillando el pool de PostgreSQL (PgBouncer Limit Reached). Se previene agrupando con un masivo SELECT * WHERE ID IN (1,2,3...N) (Batching/Dataloader).

Aunque usamos ORM, debes entender SQL básico para diagnóstico y auditoría:

sql
-- Consultar todos los usuarios de nivel 2
SELECT * FROM users WHERE level_id = 'lvl-02';

-- Contar cuántos certificados se han emitido
SELECT COUNT(*) FROM certificates;

-- Ver el progreso de un usuario específico
SELECT r.name, p.completed, p.time_spent
FROM progress p
JOIN recourses r ON p.recourse_id = r.id
WHERE p.user_id = 'abc-123';

-- Insertar un nuevo registro
INSERT INTO users (id, name, level_id) VALUES ('xyz-789', 'Ana', 'lvl-01');

-- Actualizar un campo
UPDATE users SET level_id = 'lvl-02' WHERE id = 'abc-123';

Clase 2.3: ORM — Prisma y TypeORM#

Un ORM (Object-Relational Mapping) te permite interactuar con la base de datos usando código TypeScript en lugar de SQL puro.

En SoyDigital usamos dos ORMs (por razones históricas):

  • TypeORM en dominicana-api (la API principal)
  • Prisma en brigades y crm-indotel

TypeORM (en dominicana-api)#

typescript
// Definir una entidad (tabla)
@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  full_name: string;

  @ManyToOne(() => Level)
  @JoinColumn({ name: 'current_level_id' })
  currentLevel: Level;
}

// Consultar usuarios
const users = await this.userRepository.find({
  where: { currentLevel: { order: 2 } },
  relations: ['currentLevel'],
});

Prisma (en brigades / crm-indotel)#

typescript
// schema.prisma (el modelo de datos)
model User {
  id        String   @id @default(uuid())
  fullName  String   @map("full_name")
  levelId   String   @map("current_level_id")
  level     Level    @relation(fields: [levelId], references: [id])
}

// Consultar usuarios
const users = await prisma.user.findMany({
  where: { level: { order: 2 } },
  include: { level: true },
});

Migraciones#

Son archivos SQL que modifican la estructura de la base de datos de forma controlada y versionada:

bash
# TypeORM
npm run typeorm migration:generate -- -n AddEmailColumn
npm run typeorm migration:run

# Prisma
npx prisma migrate dev --name add_email_column
npx prisma generate  # Regenera los tipos TypeScript

Clase 2.4: Transacciones#

➤ Profundización Técnica / Casos de Borde:

  • Falsos Positivos de Red: Si insertamos un "Examen Aprobado" pero la conexión Wi-Fi del ciudadano titila en el medio y cortamos antes del commit, el usuario creerá haber terminado, pero la base de datos abortará todo (Rollback). Si se programan mal las transacciones cruzadas, quedan bloqueos de escritura (Deadlocks) forzando la caída del servicio local.

Muchas operaciones en SoyDigital deben ser atómicas — o todas se ejecutan, o ninguna:

Ejemplo: Emitir un certificado implica:

  1. Crear el registro del certificado
  2. Actualizar current_level_certificated en el perfil
  3. Marcar finished_a_level = true
  4. Asignar automáticamente el siguiente nivel

Si el paso 3 falla pero el 1 y 2 ya se ejecutaron, el usuario tendría un certificado pero el sistema no lo reconocería. Las transacciones previenen esto:

typescript
await this.connection.transaction(async (manager) => {
  await manager.save(Certificate, newCertificate);
  await manager.update(UserProfile, userId, {
    current_level_certificated: levelOrder,
    finished_a_level: true,
  });
  await this.autoAssignNextLevel(manager, userId, levelOrder);
});
// Si CUALQUIERA de estos pasos falla, TODOS se deshacen (Rollback)

Nivel 3 (Senior) - Lógica de Negocio Específica de SoyDigital#

Este es el corazón de lo que hace ÚNICO a nuestro backend. Debes conocer estas reglas profundamente.


Clase 3.1: Identidad y Registro#

ConceptoRegla
Edad mínima16 años cumplidos. Se calcula con año, mes y día de nacimiento
CédulaDebe ser válida (formato RNC/Cédula Dominicana, 11 dígitos sin guiones)
UnicidadNo pueden existir dos perfiles con la misma cédula (id_number_hash)
UUIDCada usuario recibe un UUID v4 como identificador global
Internal IDPrefijo Id_ + número de documento para trazabilidad interna
Perfil completoRequiere: nombre, tipo documento, hash cédula, fecha nacimiento, género, ubicación

Flujo de autenticación:

  • Frontend público (dominicana-front, admin): Firebase AuthverifyIdToken() → Si el usuario no existe en la DB, se crea automáticamente
  • Servidor a servidor (brigades, crm): S2S Key → Header x-server-to-server-key validado contra S2S_SHARED_SECRET

Clase 3.2: Diagnóstico y Anti-Downgrade#

Tipos de diagnóstico:

TipoPropósitoCuándo se usa
ENTRYDetermina nivel inicial (1-4)Al registrarse por primera vez
FINAL_LEVELRequisito para certificar un nivelAl terminar todo el contenido de un nivel
COMPLEMENTARY_LEVELAsociado a mini-cursosAl completar un mini-curso

La regla Anti-Downgrade:

Ctrl+Scroll=Zoom · Arrastrar=Mover

Clase 3.3: Progresión Educativa y Rank de Posición#

La estructura educativa: Nivel → Módulo → Actividad → Recurso

Cálculo del Rank (avance irreversible):

Rank = ((((level_order * 1000) + module_order) * 1000) + activity_order) * 1000 + recourse_order

Ejemplo:

  • Nivel 2, Módulo 3, Actividad 1, Recurso 5:
  • Rank = ((((2 × 1000) + 3) × 1000) + 1) × 1000 + 5 = 2003001005

Regla de oro: Solo se actualiza la posición si nuevo_rank > rank_actual. Esto permite revisitar contenido sin perder progreso.

Tipos de recurso que cuentan para completitud: video, html, pdf, image, document

Tipos que NO cuentan: quiz, test, external_link


Clase 3.4: Certificación#

Tres requisitos obligatorios:

  1. 100% recursos vistos (con tolerancia LAN de ≤3 recursos)
  2. Diagnóstico FINAL_LEVEL con estado COMPLETED
  3. Mini-curso completado (solo para niveles 2, 3 y 4; nivel 1 exento)

Acciones automáticas al emitir certificado:

  1. Se crea el registro en la tabla certificates
  2. Se actualiza current_level_certificated en el perfil
  3. Se marca finished_a_level = true
  4. Se asigna automáticamente el nivel siguiente (si aplica)
  5. Se registra en auditoría (CertificateAuditService)

Tipos de auditoría:

EstadoSignificado
CERTIFICATE_CREATEDEmisión exitosa
CERTIFICATE_FAILEDError en el proceso
CERTIFICATE_FORCEDEmisión manual por administrador (salta validaciones)
PREREQUISITE_FAILEDFalta examen final o mini-curso

Clase 3.5: Autenticación y Guards#

➤ Profundización Técnica / Casos de Borde:

  • JWT Expiration Spoofing: Las firmas en la tableta deciden que expiran a un unix timestamp específico. Si la tablet cambió localmente su reloj, el JWT se volverá inmortal o permanentemente inválido (Asimetría temporal). Un Guard robusto aplica un Clock Tolerance de 2-3 minutos y revalida localmente con el Node Host en NestJS.

Dos mecanismos de autenticación:

1. Firebase Auth (Frontend → API)#

Ctrl+Scroll=Zoom · Arrastrar=Mover

2. S2S Key (Brigades/CRM → API)#

Ctrl+Scroll=Zoom · Arrastrar=Mover

Rate Limiting: 100 requests/min por IP en producción (GraphQL de brigades).


Clase 3.6: Sincronización S2S y Modo Offline#

➤ Profundización Técnica / Casos de Borde:

  • Algoritmos CRDT y Last-Write-Wins: Cuando 20 Kits envían bases locales a la Nube al mismo tiempo en la madrugada, ¿Quién gana si una cédula fue alterada en Santo Domingo y en La Vega el mismo día? La API maneja estrategias de Vector-Clock (Marcas de Tiempo Universales con IDs en lugar de auto-incrementales Integer) para fusionar sin quebrar llaves foráneas (UUIDv4).

El mayor desafío técnico del proyecto: que los datos de campo lleguen a la nube sin perderse.

Estrategia:

  1. Offline: El frontend guarda eventos en localStorage key offlineProgress con clientRequestId (UUID v4)
  2. Detección de conectividad: El frontend monitorea eventos online/offline del navegador
  3. Sincronización: Al detectar internet, envía lotes de 50 registros a la API cloud
  4. Idempotencia: clientRequestId evita duplicados — si la misma petición llega dos veces, se ignora
  5. Resolución de conflictos:
    • MERGE (default): Suma intentos y tiempo local al total del servidor
    • SERVER_WINS: El servidor tiene datos más avanzados, se descarta el local
    • CLIENT_WINS: El local es verificablemente más reciente

Zona horaria: America/Santo_Domingo (UTC-4). Registros antes del 2026-02-05 almacenados erróneamente en UTC requieren conversión explícita.


Nivel 4 (Arquitecto/Experto) - Observabilidad, Testing y Despliegue#


Clase 4.1: Manejo de Errores y Observabilidad#

GlobalExceptionFilter: Intercepta TODAS las excepciones antes de que lleguen al cliente.

Cuando ocurre un error:

  1. Se extrae el contexto educativo (user_id, level_id, module_id, activity_id, recourse_id)
  2. Se persiste en tabla error_log de PostgreSQL
  3. Se clasifica por origen: API, SYNC, AUTH, OTHER
  4. Se clasifica por severidad: CRITICAL, ERROR, WARNING
  5. Se devuelve al cliente un JSON estandarizado con error_id único
json
{
  "statusCode": 500,
  "timestamp": "2026-03-02T16:00:00.000Z",
  "path": "/api/progress/complete",
  "message": "Error interno del servidor",
  "error_id": "UUID-UNICO-DE-TRAZA"
}

Cada petición HTTP recibe un Request ID que se propaga por todos los logs para trazabilidad.


Clase 4.2: Testing con Jest#

Los tests son obligatorios antes de mergear a main. Archivos *.spec.ts.

typescript
describe('CertificateService', () => {
  it('debe emitir certificado si todos los requisitos se cumplen', async () => {
    // Arrange: preparar datos mock
    mockUserService.findById.mockResolvedValue(userConProgreso100);
    mockDiagnosticService.hasFinalLevel.mockResolvedValue(true);
    mockMiniCourseService.hasCompleted.mockResolvedValue(true);

    // Act: ejecutar la función
    const result = await certificateService.create(userId, levelId);

    // Assert: verificar resultado
    expect(result).toBeDefined();
    expect(result.type).toBe(CertificateType.LEVEL_COMPLETION);
    expect(mockAuditService.log).toHaveBeenCalledWith('CERTIFICATE_CREATED');
  });

  it('debe fallar si falta el examen final', async () => {
    mockDiagnosticService.hasFinalLevel.mockResolvedValue(false);

    await expect(certificateService.create(userId, levelId))
      .rejects.toThrow('PREREQUISITE_FAILED');
  });
});

Mocking: "Fingimos" el comportamiento de la base de datos y servicios externos para aislar la lógica que estamos probando.


Clase 4.3: Dockerfile Multi-Stage#

➤ Profundización Técnica / Casos de Borde:

  • Imágenes Distroless Node limitando la Vulnerabilidad: Utilizando un multi-stage se destruye el compilador TypeScript en Producción (devDependencies), y la imagen colapsa de 1.8GB a 180MB. Esto es imperativo porque Starlink descarga las actualizaciones de Docker y los poblados remotos pierden la conexión constante.
dockerfile
# Stage 1: Builder (instala dependencias + compila TypeScript)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production (solo lo necesario para correr)
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 7000
CMD ["node", "dist/main.js"]

¿Por qué multi-stage? La imagen final es mucho más pequeña — no incluye TypeScript, herramientas de build, ni código fuente.


Clase 4.4: CI/CD#

Cuando se fusiona código a main:

  1. GitHub Action descarga el código
  2. Ejecuta npm run test — si falla, se detiene todo
  3. Ejecuta npm run build — compila TypeScript
  4. Construye la imagen Docker: docker build -t mi-api:v13 .
  5. Push al Image Registry (GCP Artifact Registry)
  6. Despliega a Google Cloud Run (auto-escala, zero downtime)

Clase 4.5: Variables de Entorno Críticas#

VariablePropósito
DATABASE_URLCadena de conexión a PostgreSQL
FIREBASE_PROJECT_IDProyecto de Firebase para autenticación
S2S_SHARED_SECRETClave compartida para autenticación servidor-a-servidor
CERTIFICATE_MISSING_PROGRESS_TOLERANCETolerancia LAN (default: 3)
JWT_SECRETSecreto para firmar tokens JWT internos
NODE_ENVdevelopment / production

Práctica Obligatoria del Nuevo Ingreso#

bash
# 1. Clonar el repositorio
git clone <url-del-repo> dominicana-api
cd dominicana-api

# 2. Instalar dependencias
npm install

# 3. Levantar PostgreSQL local
npm run db:docker:up

# 4. Aplicar migraciones (crea las tablas)
npm run db:reset-and-migrate

# 5. Sembrar datos de prueba
npm run db:seed

# 6. Ejecutar tests unitarios
npm run test

# 7. Iniciar en modo desarrollo
npm run start:dev

# 8. Probar un endpoint
curl http://localhost:7000/api/health
# Esperado: { "status": "ok" }

# 9. Ver la documentación Swagger
# Abrir en el navegador: http://localhost:7000/api-docs

Glosario del Desarrollador Backend#

TérminoSignificado
Anti-DowngradeRegla que impide que un usuario baje de nivel por un nuevo diagnóstico
Bearer TokenToken de autenticación que se envía en el header Authorization
CRUDCreate, Read, Update, Delete — las 4 operaciones básicas
DTOData Transfer Object — contrato de datos de entrada para un endpoint
GuardMiddleware que decide si una petición tiene permiso de pasar
IdempotenciaUna operación que produce el mismo resultado si se ejecuta una o muchas veces
JWTJSON Web Token — credencial digital firmada criptográficamente
MigraciónArchivo SQL versionado que modifica la estructura de la base de datos
ORMObject-Relational Mapping — abstracción para interactuar con la DB en código
Rank de PosiciónValor numérico que codifica la posición exacta del usuario en la jerarquía educativa
S2SServer-to-Server — comunicación directa entre servidores con clave compartida
SeedDatos iniciales de prueba insertados en la base de datos
Tolerancia LANMargen de hasta 3 recursos no vistos que se permite para certificar
TransacciónOperación atómica: o todas las escrituras se ejecutan, o ninguna


Ruta de Dominio y Escalafón Profesional (Matriz de Habilidades Backend)#

La complejidad del backend Indotel no radica en escalarlo a millones de QPS locales, sino en la distribución resiliente sobre redes defectuosas en los Edge Locations.

Nivel Junior (Arquitectura de Servicios Base)#

  1. Tipado Estricto (TypeScript): Dominio sólido del sistema tsconfig de NestJS. Crear controladores, servicios, provisión de dependencias (DI) e implementaciones de DTOs utilizando class-validator y Pipes.
  2. Prisma ORM Local Framework: Entender migraciones locales en SQLite (npx prisma migrate deploy vs dev). Escribir consultas eficientes y mapeos de datos.
  3. Ecosistema PM2 Básico: Saber orquestar aplicaciones locales en Node. Observar logs pm2 logs api, recargar el proceso (pm2 reload) en los ambientes del Mini-PC.

Nivel Semi-Senior (Lógica Offline y Concurrencia Distribuida)#

  1. Arquitectura Offline WAL: Entender el Journal Mode = WAL (Write-Ahead Logging) en SQLite. Soportar múltiples lecturas y escrituras atómicas simultáneas sin chocar con "Db is locked", implementando retry statements y retrasos backoff en transacciones pesadas.
  2. Workers de Fila (Queues): Programación sólida con Redis/BullMQ u otro Broker asíncrono para ingesta de datos. Los Brigadistas van con sus Tablets al Edge (Mini-PC) en masa a final del turno; la API no guarda sincrónico a Nube sino que encola hacia "Push Sync Queues".
  3. Seguridad S2S (Autenticación Inter-Node): Dominio asimétrico JWT o TLS mutuo entre el Hub (AWS/GCP) y el Node. Capacidad de rotación de credenciales distribuidas.

Nivel Senior/Arquitecto (Idempotencia y Eventual Consistency)#

  1. CRDTs y Conflict Resolution: En modo híbrido, un campo modificado localmente podría ser modificado en un sub-panel de Hub por un Analista. El Senior domina algoritmos vectoriales de tiempo o reglas LWW (Last Write Wins) implementando timestamps seguros (deleted_at, versions, revisions) contra la base sincronizada central de PostgreSQL en la nube.
  2. Resiliencia Extrema en Starlink: Desarrolo de protocolos usando WebSockets y Multiplexación TCP, con manejos agresivos de reintento exponencial asimétrico para los envíos Batch a la Nube evadiendo la alta latencia variable y microcortes del Starlink.
  3. Deep Profiling en Ambientes Endémicos: Capacidad para conectar ndb (Node Debugger), analizar Snapshot de memoria/heap dumps remotos cuando el OOM (Out Of Memory) de un Mini-PC de 4GB colapsa la API.

Escenarios Críticos y Troubleshooting (Edge Cases)#

1. Desincronización Severa de la BD Local (SQLite WAL Locks)

  • Síntoma: El M.2 experimenta bloqueos. La BD lanza SQLITE_BUSY persistente. Sensores físicos/IoT fallan inserciones.
  • Protocolo de Acción (Backend):
    1. Matar procesos huérfanos que hayan capturado de modo tonto el socket. lsof | grep sqlite
    2. Implementación de una sub-rutina de recuperación (.bak), realizar validación PRAGMA PRAGMA integrity_check;. Si devuelve corrompido, usar CLI SQLite para hacer un .recover in-place, restaurar y reiniciar el contenedor / pm2.

2. Asfixia por Race-Conditions en Colas S2S (API rechazada permanentemente)

  • Síntoma: Status 401 del Auth Cloud repetidamente. La memoria (RAM) se consume alojando todos los batch pendientes hasta morir (Memory Leak de colas de eventos).
  • Protocolo de Acción:
    • El reloj de la BIOS del Mini-PC (CMOS agotado) causó que el NTP se desvíe. El Backend genera JWT "caducados en el pasado" (exp claim). El Backend dev implementa mitigación detectando el time spoof y exige al OS refrescar la hora vía shell (timedatectl set-ntp true). Además limita la memoria del heap en .pm2 config con las banderas --max-old-space-size=... forzando recargas seguras que preservan memoria en disco para evitar colapsos masivos.