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:
¿Por qué no consultar la base de datos directamente desde el teléfono?#
- Seguridad: La base de datos estaría expuesta a todo el mundo
- Validación: Cualquiera podría auto-asignarse notas perfectas
- Consistencia: Las reglas de negocio (anti-downgrade, tolerancia LAN, etc.) viven en un solo lugar
- 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:
| Concepto | Explicación | Ejemplo |
|---|---|---|
| URL | La dirección del recurso | https://api.soydigital.gob.do/api/users/123 |
| Método/Verbo | La acción que quieres hacer | GET, POST, PUT, PATCH, DELETE |
| Headers | Metadatos de la petición | Authorization: Bearer eyJ... (token de autenticación) |
| Body | Los datos que envías | { "cedula": "40200000000", "nombre": "Juan" } |
| Status Code | El resultado del servidor | 200 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étodo | Acción | Ejemplo en SoyDigital | ¿Modifica datos? |
|---|---|---|---|
GET | Obtener datos | Consultar el progreso de un usuario | No |
POST | Crear algo nuevo | Registrar un nuevo usuario | Sí |
PUT | Reemplazar completamente | Actualizar todo el perfil | Sí |
PATCH | Actualizar parcialmente | Cambiar solo el email | Sí |
DELETE | Eliminar | Borrar un intento de diagnóstico | Sí |
¿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#
| Concepto | Qué es | Ejemplo |
|---|---|---|
| Interface | Contrato de forma de un objeto | interface User { name: string; age: number; } |
| Enum | Conjunto fijo de opciones | enum Role { ADMIN, USER, FACILITATOR } |
| Generic | Tipo paramétrico/reutilizable | Array<User> — un array donde cada elemento es User |
Optional (?) | El campo puede no existir | email?: string — puede ser string o undefined |
| Type Guard | Verificación de tipo en runtime | if (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.parsede 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 oworker_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#
| Archivo | Función |
|---|---|
package.json | Manifiesto del proyecto: nombre, versión, dependencias, scripts |
package-lock.json | Versiones exactas de CADA dependencia (para reproducibilidad) |
node_modules/ | Carpeta donde se descargan las librerías (NUNCA se sube a Git) |
tsconfig.json | Configuración de TypeScript (qué tan estricto, target de compilación) |
.env | Variables de entorno (secretos, URLs, configuraciones) |
Scripts que encontrarás en dominicana-api:#
bashnpm 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#
| Pilar | Decorador | Responsabilidad | Analogía |
|---|---|---|---|
| Módulo | @Module | Agrupa Controller + Service + providers relacionados | Un departamento de la empresa |
| Controlador | @Controller | Recibe HTTP, valida datos de entrada, llama al servicio, devuelve respuesta | La recepcionista |
| Servicio | @Injectable | Contiene la lógica de negocio real. AQUÍ viven las reglas | El analista que hace el trabajo |
| Guard | @UseGuards | Decide si una petición tiene permiso de pasar | El 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:
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:#
Relaciones clave:
- Un usuario pertenece a un nivel →
users.level_idapunta alevels.id - Un usuario tiene muchos registros de progreso →
progress.user_idapunta ausers.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 ejecuteSELECT cedula FROM usersgenerará 10,000 conexiones separadas arrodillando el pool de PostgreSQL (PgBouncer Limit Reached). Se previene agrupando con un masivoSELECT * 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
brigadesycrm-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:
- Crear el registro del certificado
- Actualizar
current_level_certificateden el perfil - Marcar
finished_a_level = true - 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:
typescriptawait 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#
| Concepto | Regla |
|---|---|
| Edad mínima | 16 años cumplidos. Se calcula con año, mes y día de nacimiento |
| Cédula | Debe ser válida (formato RNC/Cédula Dominicana, 11 dígitos sin guiones) |
| Unicidad | No pueden existir dos perfiles con la misma cédula (id_number_hash) |
| UUID | Cada usuario recibe un UUID v4 como identificador global |
| Internal ID | Prefijo Id_ + número de documento para trazabilidad interna |
| Perfil completo | Requiere: nombre, tipo documento, hash cédula, fecha nacimiento, género, ubicación |
Flujo de autenticación:
- Frontend público (dominicana-front, admin): Firebase Auth →
verifyIdToken()→ Si el usuario no existe en la DB, se crea automáticamente - Servidor a servidor (brigades, crm): S2S Key → Header
x-server-to-server-keyvalidado contraS2S_SHARED_SECRET
Clase 3.2: Diagnóstico y Anti-Downgrade#
Tipos de diagnóstico:
| Tipo | Propósito | Cuándo se usa |
|---|---|---|
ENTRY | Determina nivel inicial (1-4) | Al registrarse por primera vez |
FINAL_LEVEL | Requisito para certificar un nivel | Al terminar todo el contenido de un nivel |
COMPLEMENTARY_LEVEL | Asociado a mini-cursos | Al completar un mini-curso |
La regla Anti-Downgrade:
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:
- 100% recursos vistos (con tolerancia LAN de ≤3 recursos)
- Diagnóstico
FINAL_LEVELcon estadoCOMPLETED - Mini-curso completado (solo para niveles 2, 3 y 4; nivel 1 exento)
Acciones automáticas al emitir certificado:
- Se crea el registro en la tabla
certificates - Se actualiza
current_level_certificateden el perfil - Se marca
finished_a_level = true - Se asigna automáticamente el nivel siguiente (si aplica)
- Se registra en auditoría (
CertificateAuditService)
Tipos de auditoría:
| Estado | Significado |
|---|---|
CERTIFICATE_CREATED | Emisión exitosa |
CERTIFICATE_FAILED | Error en el proceso |
CERTIFICATE_FORCED | Emisión manual por administrador (salta validaciones) |
PREREQUISITE_FAILED | Falta 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 timestampespecífico. Si la tablet cambió localmente su reloj, el JWT se volverá inmortal o permanentemente inválido (Asimetría temporal). Un Guard robusto aplica unClock Tolerancede 2-3 minutos y revalida localmente con el Node Host en NestJS.
Dos mecanismos de autenticación:
1. Firebase Auth (Frontend → API)#
2. S2S Key (Brigades/CRM → API)#
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:
- Offline: El frontend guarda eventos en
localStoragekeyofflineProgressconclientRequestId(UUID v4) - Detección de conectividad: El frontend monitorea eventos
online/offlinedel navegador - Sincronización: Al detectar internet, envía lotes de 50 registros a la API cloud
- Idempotencia:
clientRequestIdevita duplicados — si la misma petición llega dos veces, se ignora - 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:
- Se extrae el contexto educativo (
user_id,level_id,module_id,activity_id,recourse_id) - Se persiste en tabla
error_logde PostgreSQL - Se clasifica por origen:
API,SYNC,AUTH,OTHER - Se clasifica por severidad:
CRITICAL,ERROR,WARNING - 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.
typescriptdescribe('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:
- GitHub Action descarga el código
- Ejecuta
npm run test— si falla, se detiene todo - Ejecuta
npm run build— compila TypeScript - Construye la imagen Docker:
docker build -t mi-api:v13 . - Push al Image Registry (GCP Artifact Registry)
- Despliega a Google Cloud Run (auto-escala, zero downtime)
Clase 4.5: Variables de Entorno Críticas#
| Variable | Propósito |
|---|---|
DATABASE_URL | Cadena de conexión a PostgreSQL |
FIREBASE_PROJECT_ID | Proyecto de Firebase para autenticación |
S2S_SHARED_SECRET | Clave compartida para autenticación servidor-a-servidor |
CERTIFICATE_MISSING_PROGRESS_TOLERANCE | Tolerancia LAN (default: 3) |
JWT_SECRET | Secreto para firmar tokens JWT internos |
NODE_ENV | development / 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érmino | Significado |
|---|---|
| Anti-Downgrade | Regla que impide que un usuario baje de nivel por un nuevo diagnóstico |
| Bearer Token | Token de autenticación que se envía en el header Authorization |
| CRUD | Create, Read, Update, Delete — las 4 operaciones básicas |
| DTO | Data Transfer Object — contrato de datos de entrada para un endpoint |
| Guard | Middleware que decide si una petición tiene permiso de pasar |
| Idempotencia | Una operación que produce el mismo resultado si se ejecuta una o muchas veces |
| JWT | JSON Web Token — credencial digital firmada criptográficamente |
| Migración | Archivo SQL versionado que modifica la estructura de la base de datos |
| ORM | Object-Relational Mapping — abstracción para interactuar con la DB en código |
| Rank de Posición | Valor numérico que codifica la posición exacta del usuario en la jerarquía educativa |
| S2S | Server-to-Server — comunicación directa entre servidores con clave compartida |
| Seed | Datos iniciales de prueba insertados en la base de datos |
| Tolerancia LAN | Margen de hasta 3 recursos no vistos que se permite para certificar |
| Transacción | Operació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)#
- Tipado Estricto (TypeScript): Dominio sólido del sistema
tsconfigde NestJS. Crear controladores, servicios, provisión de dependencias (DI) e implementaciones de DTOs utilizandoclass-validatory Pipes. - Prisma ORM Local Framework: Entender migraciones locales en SQLite (
npx prisma migrate deployvsdev). Escribir consultas eficientes y mapeos de datos. - 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)#
- 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. - 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".
- 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)#
- 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. - 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.
- 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_BUSYpersistente. Sensores físicos/IoT fallan inserciones. - Protocolo de Acción (Backend):
- Matar procesos huérfanos que hayan capturado de modo tonto el socket.
lsof | grep sqlite - Implementación de una sub-rutina de recuperación (
.bak), realizar validación PRAGMAPRAGMA integrity_check;. Si devuelve corrompido, usar CLI SQLite para hacer un.recoverin-place, restaurar y reiniciar el contenedor / pm2.
- Matar procesos huérfanos que hayan capturado de modo tonto el socket.
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 spoofy exige al OS refrescar la hora vía shell (timedatectl set-ntp true). Además limita la memoria del heap en.pm2 configcon las banderas--max-old-space-size=...forzando recargas seguras que preservan memoria en disco para evitar colapsos masivos.
- 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