27 Commits

Author SHA1 Message Date
a95655a2a6 Completada la tarea de volcado 2026-03-26 09:29:09 +01:00
025801a689 Repositorio de lineas funciona 2026-03-25 11:51:14 +01:00
28880c4d99 Lineas activas e insertar cada una 2026-03-24 17:27:52 +01:00
5bb3bc554b doc 2026-03-24 11:20:59 +01:00
cfb907b840 Copy en jenkins 2026-03-11 12:34:41 +01:00
d5d7953fd2 Endpoint para documentacion 2026-03-11 12:31:17 +01:00
96298aab25 Docs en HTML 2026-03-11 11:35:16 +01:00
c17cca1e81 Sobreescribia el registerSobreescribia el register 2026-03-10 10:43:56 +01:00
7264efcf79 Errata 2026-03-10 10:42:32 +01:00
8934bcd603 Copia yarnrc 2026-03-10 10:39:39 +01:00
bdd08dbc56 Copiar yarnrc a docker 2026-03-10 10:37:26 +01:00
7d47fde806 Solucionado problema db-migrate 2026-03-10 10:21:53 +01:00
ad207fb732 db-migrate 2026-03-10 09:34:17 +01:00
bd9081b5bc hardcodeado el customerAccountCode 2026-03-06 11:18:30 +01:00
a429e9d14a Errata customer 2026-03-06 11:13:47 +01:00
81eb986313 Error de tipado 2026-03-06 11:06:15 +01:00
58bedc42f1 Bug de correlation_id en las llamadas a objenious 2026-03-06 11:02:18 +01:00
b97f422261 Prod 2026-03-05 10:33:38 +01:00
7a7dc33724 Error de prod 2026-03-05 10:30:43 +01:00
7743bd1f0d Migraciones a mano de momento 2026-03-05 10:17:26 +01:00
2897d7aa3c Probando a añadir el registro desde jenkins 2026-03-05 10:10:12 +01:00
0fd7eafcf3 Eliminado el clone 2026-03-05 09:09:02 +01:00
71253d216e Registry local 2026-03-05 09:04:50 +01:00
aeea6cfefd Probando con clone 2026-03-04 16:49:07 +01:00
e8eb925834 Sin paso 2026-03-04 15:54:13 +01:00
7cf9cc60e6 Test jenkins 2026-03-04 15:52:43 +01:00
1e9818d430 Yarn lock 2026-03-04 14:03:11 +01:00
28 changed files with 627 additions and 83 deletions

View File

@@ -3,3 +3,9 @@ compressionLevel: mixed
enableGlobalCache: false enableGlobalCache: false
nodeLinker: node-modules nodeLinker: node-modules
npmScopes:
sf-alvar:
npmRegistryServer: "https://git.savefamilygps.net/api/packages/SaveFamily/npm/"
npmRegistryServer: "https://registry.npmjs.org/"

View File

@@ -1,10 +1,3 @@
#/bin/bash #/bin/bash
rm deployment/database/init.sql
# cat deployment/database/*.sql >deployment/database/init.sql
cp deployment/database/esquema_final* deployment/database/init.sql
# compatibilidad con postgresql < 17
sed -i '/\\restrict/d' deployment/database/init.sql
sed -i '/\\unrestrict/d' deployment/database/init.sql
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ build docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ build

View File

@@ -0,0 +1,20 @@
CREATE table if not exists objenious_lines (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
simId BIGINT UNIQUE,
status TEXT,
iccid TEXT NOT NULL,
msisdn TEXT,
imei TEXT,
imeiChangeDate TIMESTAMPTZ,
offerCode TEXT,
preactivationDate TIMESTAMPTZ, -- No viene con hora
activationDate TIMESTAMPTZ,
commercialStatus TEXT,
commercialStatusDate TIMESTAMPTZ,
billingStatus TEXT,
billingStatusChangeDate TIMESTAMPTZ,
billingActivationDate TIMESTAMPTZ,
createDate TIMESTAMPTZ,
raw JSONB,
hash TEXT
)

View File

@@ -6,12 +6,11 @@ WORKDIR /home/node/app
RUN corepack enable RUN corepack enable
COPY ./dist/packages ./packages COPY ./dist/packages ./packages
COPY ./.yarnrc.yml ./
COPY ./docs ./docs
COPY ./package.json ./ COPY ./package.json ./
# Force node-modules linker (no .yarnrc.yml in build context)
RUN echo 'nodeLinker: node-modules' > .yarnrc.yml
RUN yarn install RUN yarn install
RUN mkdir -p dist && ln -sf ../packages dist/packages RUN mkdir -p dist && ln -sf ../packages dist/packages

View File

@@ -17,12 +17,12 @@ pipeline {
sh 'npm install -g yarn' sh 'npm install -g yarn'
sh 'corepack enable' sh 'corepack enable'
sh 'corepack prepare yarn@4.12.0 --activate' sh 'corepack prepare yarn@4.12.0 --activate'
sh 'yarn install' sh 'yarn install --immutable'
} }
} }
stage("🧱 Building") { stage("🧱 Building") {
steps { steps {
sh 'rm -rf dist/' sh 'rm -rf dist/'
sh 'yarn run build' sh 'yarn run build'
} }
} }
@@ -60,6 +60,11 @@ pipeline {
sourceFiles: "dist/**/*", sourceFiles: "dist/**/*",
excludes: "dist/**/node_modules/**" excludes: "dist/**/node_modules/**"
), ),
sshTransfer(
cleanRemote: false,
remoteDirectory: "$APP_REMOTE_PATH",
sourceFiles: "docs/**/*",
),
sshTransfer( sshTransfer(
cleanRemote: false, cleanRemote: false,
remoteDirectory: "$APP_REMOTE_PATH", remoteDirectory: "$APP_REMOTE_PATH",
@@ -88,6 +93,11 @@ pipeline {
remoteDirectory: "$APP_REMOTE_PATH", remoteDirectory: "$APP_REMOTE_PATH",
sourceFiles: "package.json", sourceFiles: "package.json",
), ),
sshTransfer(
cleanRemote: false,
remoteDirectory: "$APP_REMOTE_PATH",
sourceFiles: ".yarnrc.yml",
),
sshTransfer( sshTransfer(
cleanRemote: false, cleanRemote: false,
execCommand: "sh $APP_REMOTE_PATH/rebuild.sh" execCommand: "sh $APP_REMOTE_PATH/rebuild.sh"

View File

@@ -14,6 +14,7 @@ COPY ./packages ./packages
COPY tsconfig*.json ./ COPY tsconfig*.json ./
COPY .env* ./ COPY .env* ./
COPY ./.yarnrc.yml ./ COPY ./.yarnrc.yml ./
COPY ./docs ./docs
COPY ./deployment/local/docker/start.sh ./ COPY ./deployment/local/docker/start.sh ./
# Copiar el archivo de migrations? porque ahora no creo que se esté lanzando nada # Copiar el archivo de migrations? porque ahora no creo que se esté lanzando nada
COPY ./deployment/database/migrations ./deployment/database/migrations COPY ./deployment/database/migrations ./deployment/database/migrations

View File

@@ -40,6 +40,9 @@ services:
- path: ./packages - path: ./packages
action: sync action: sync
target: /usr/local/app/packages target: /usr/local/app/packages
- path: ./docs
action: sync
target: /usr/local/app/docs
- path: ./package.json - path: ./package.json
action: rebuild action: rebuild
ports: ports:
@@ -72,7 +75,6 @@ services:
- "${POSTGRES_PORT}:${POSTGRES_PORT}" - "${POSTGRES_PORT}:${POSTGRES_PORT}"
volumes: volumes:
- ./sql-data/:/var/lib/postgres/data - ./sql-data/:/var/lib/postgres/data
- ./deployment/database/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s interval: 5s

File diff suppressed because one or more lines are too long

16
docs/sim-api/Docs.bru Normal file
View File

@@ -0,0 +1,16 @@
meta {
name: Docs
type: http
seq: 12
}
get {
url: {{baseurl}}/docs/sim-api-documentation.html
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -5,16 +5,16 @@ meta {
} }
get { get {
url: https://api-getway.objenious.com/ws/lines?pageSize=10&identifier.identifierType=ICCID&identifier.identifiers=8933201125065160455 url: https://api-getway.objenious.com/ws/lines?pageSize=1000&simStatus=ACTIVATED
body: formUrlEncoded body: formUrlEncoded
auth: bearer auth: bearer
} }
params:query { params:query {
pageSize: 10 pageSize: 1000
identifier.identifierType: ICCID simStatus: ACTIVATED
identifier.identifiers: 8933201125065160455 ~identifier.identifierType: ICCID
~simStatus: ACTIVATED ~identifier.identifiers: 8933201125065160455
} }
auth:bearer { auth:bearer {

View File

@@ -37,7 +37,7 @@ body:form-urlencoded {
} }
vars:pre-request { vars:pre-request {
params.id: 14557 params.id: 15102
} }
settings { settings {

View File

@@ -19,12 +19,12 @@
"migrate": "yarn db-migrate -e .env -m deployment/database/migrations -t 99.0.0" "migrate": "yarn db-migrate -e .env -m deployment/database/migrations -t 99.0.0"
}, },
"dependencies": { "dependencies": {
"@sf-alvar/db-migrate": "1.0.3",
"@tsconfig/node22": "^22.0.5", "@tsconfig/node22": "^22.0.5",
"amqp-connection-manager": "^5.0.0", "amqp-connection-manager": "^5.0.0",
"amqplib": "^0.10.9", "amqplib": "^0.10.9",
"axios": "^1.13.3", "axios": "^1.13.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"db-migrate": "http://gitea:3000/alvarsanmartin/herramienta-migracion.git",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"express": "^5.2.1", "express": "^5.2.1",
"pg": "^8.18.0", "pg": "^8.18.0",

View File

@@ -86,7 +86,7 @@ export class SimController {
const resp = await this.tryUseCase(msg, this.useCases.activate({ const resp = await this.tryUseCase(msg, this.useCases.activate({
correlation_id: msgData.headers?.message_id, correlation_id: msgData.headers?.message_id,
dueDate: this.genDueDate(DUE_DATE_SECONDS).toISOString(), dueDate: this.genDueDate(DUE_DATE_SECONDS).toISOString(),
customerAccountCode: env.OBJ_CUSTOMER_CODE, customerAccountCode: "9.49411.10",
identifier: { identifier: {
identifierType: "ICCID", identifierType: "ICCID",
identifiers: [iccid] identifiers: [iccid]

View File

@@ -110,7 +110,10 @@ export class SimUseCases {
const OPERATION_URL = "/actions/activateLine" const OPERATION_URL = "/actions/activateLine"
return async () => { return async () => {
const req = this.httpClient.client.post(OPERATION_URL, { const req = this.httpClient.client.post(OPERATION_URL, {
...activationData dueDate: activationData.dueDate,
identifier: activationData.identifier,
customerAccountCode: activationData.customerAccountCode,
offer: activationData.offer
}) })
try { try {
@@ -225,7 +228,10 @@ export class SimUseCases {
const OPERATION_URL = "/actions/suspendLine" const OPERATION_URL = "/actions/suspendLine"
return this.generateUseCase({ return this.generateUseCase({
correlation_id: suspendData.correlation_id, correlation_id: suspendData.correlation_id,
operationPayload: suspendData, operationPayload: {
dueDate: suspendData.dueDate,
identifier: suspendData.identifier
},
url: OPERATION_URL, url: OPERATION_URL,
iccid: suspendData.identifier.identifiers[0], // iccid: suspendData.identifier.identifiers[0], //
operation: "suspend" operation: "suspend"
@@ -236,7 +242,10 @@ export class SimUseCases {
const OPERATION_URL = "/actions/terminateLine" const OPERATION_URL = "/actions/terminateLine"
return this.generateUseCase({ return this.generateUseCase({
correlation_id: terminationData.correlation_id, correlation_id: terminationData.correlation_id,
operationPayload: terminationData, operationPayload: {
dueDate: terminationData.dueDate,
identifier: terminationData.identifier
},
url: OPERATION_URL, url: OPERATION_URL,
iccid: terminationData.identifier.identifiers[0], // iccid: terminationData.identifier.identifiers[0], //
operation: "terminate" operation: "terminate"

View File

@@ -79,7 +79,7 @@ export class SimController {
...usecaseResult.error ...usecaseResult.error
} }
}).send() }).send()
args.onError(body, usecaseResult.error.msg.message) args.onError(body, usecaseResult.error ?? "Error indefinido")
return 1; return 1;
} }

View File

@@ -198,7 +198,7 @@ export class SimUsecases {
* alias de bloquear / suspender en objenious * alias de bloquear / suspender en objenious
*/ */
async pause(args: { iccid: string, compañia: string }): async pause(args: { iccid: string, compañia: string }):
Promise<Result<string, { iccid: string, message_id: string, operation: "cancelation" }>> { Promise<Result<string, { iccid: string, message_id: string, operation: "pause" }>> {
const pauseEvent = <SimEvents.pause>{ const pauseEvent = <SimEvents.pause>{
key: `sim.${args.compañia}.pause`, key: `sim.${args.compañia}.pause`,
payload: { payload: {
@@ -222,7 +222,7 @@ export class SimUsecases {
data: { data: {
iccid: args.iccid, iccid: args.iccid,
message_id: savedOrder.data.correlation_id, message_id: savedOrder.data.correlation_id,
operation: "cancelation" operation: "pause"
} }
} }
} }

View File

@@ -1,5 +1,6 @@
import express from "express" import express from "express"
import cors from 'cors'; import cors from 'cors';
import path from 'path';
import { simRoutes } from "./infrastructure/simRoutes.http.js" import { simRoutes } from "./infrastructure/simRoutes.http.js"
import { rabbitmqEventBus } from '#config/eventBusConfig.js'; import { rabbitmqEventBus } from '#config/eventBusConfig.js';
import { env } from "#config/env/index.js" import { env } from "#config/env/index.js"
@@ -27,6 +28,8 @@ app.use(express.urlencoded({ extended: true }));
app.use("/sim", simRoutes) app.use("/sim", simRoutes)
app.use("/orders", orderRoutes) app.use("/orders", orderRoutes)
app.use("/docs", express.static(path.join(process.cwd(), '../../docs')))
app.get("/health", (req, res) => { app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" }) res.status(200).json({ status: "ok" })
}) })

View File

@@ -0,0 +1,20 @@
/**
* Cliente de postgres para la intranet. Se usa solo porque hace falta para el
* volcado de datos, si se usa en mas partes algo estás haciendo mal.
*/
import { Pool } from 'pg';
import { PgClient } from 'sim-shared/infrastructure/PgClient.js'
import { env } from './env/index.js';
export const pgPoolIntranet = new Pool({
user: env.POSTGRES_USER,
host: env.POSTGRES_HOST,
database: "postgres",
password: env.POSTGRES_PASSWORD,
port: Number(env.POSTGRES_PORT) || 5432,
});
export const postgresClientIntranet = new PgClient({
pool: pgPoolIntranet
})

View File

@@ -5,6 +5,9 @@ import { httpInstance } from "./config/httpClient.config.js"
import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js" import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js"
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js" import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js" import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
import { TaskVolcadoLineas } from "./tasks/volcado_lineas.js"
import { ObjeniousLinesRepository } from "./infranstructure/ObjeniousLinesRepository.js"
import { postgresClientIntranet } from "./config/intranetPostgresConfig.js"
async function startCron() { async function startCron() {
const commonSettings = { const commonSettings = {
@@ -14,10 +17,13 @@ async function startCron() {
const httpClient = httpInstance const httpClient = httpInstance
const pgClient = new PgClient({ pool: pgPool }) const pgClient = new PgClient({ pool: pgPool })
console.log("[i] Comprobando conexion con la BDD ")
await pgClient.checkDatabaseConnection() await pgClient.checkDatabaseConnection()
await pgClient.checkDatabaseConnection()
const operationRepository = new ObjeniousOperationsRepository(pgClient) const operationRepository = new ObjeniousOperationsRepository(pgClient)
const orderRepository = new OrderRepository(pgClient) const orderRepository = new OrderRepository(pgClient)
const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet)
const objTask = new CheckObjeniousRequests( const objTask = new CheckObjeniousRequests(
operationRepository, operationRepository,
@@ -25,23 +31,28 @@ async function startCron() {
httpClient, httpClient,
) )
await objTask.getPendingOperations() const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository)
const PERIODO_PETICIONES = 10 * 60 * 60
const interval = setInterval(async () => { const interval = setInterval(async () => {
console.log("Updating...") try {
await objTask.getPendingOperations() await objTask.getPendingOperations()
console.log("Update finished") } catch (e) {
}, 10 * 60 * 1000) console.error("[x] Error de actualizacion de las lineas ")
/* }
const task = cron.createTask("* * * * *", async () => { }, PERIODO_PETICIONES)
}
, { const PERIODO_VOLCADO = 60 * 60 * 1000
...commonSettings, const volcadoInterval = setInterval(async () => {
name: "Test" try {
}) await volcadoLineasTask.loadLines()
*/ } catch (e) {
console.error("[x] Volcado de lineas de Objenious Fallido", e)
}
}, PERIODO_VOLCADO)
await volcadoLineasTask.loadLines()
//await objTask.getPendingOperations()
} }

View File

@@ -0,0 +1,59 @@
import test, { after, before, describe } from "node:test";
import { CreateObjeniousLineDTO } from "sim-shared/domain/objeniousLine.js";
import { ObjeniousLinesRepository } from "./ObjeniousLinesRepository.js";
import { postgrClient } from "../config/postgreConfig.js";
import assert from "node:assert";
describe("Line insertion test", async () => {
//const pgClient = postgreClientIntranet
const pgClient = postgrClient // En prod hay que usar el de Intrantet para usar la otra base de datos
const lineRepository = new ObjeniousLinesRepository(pgClient)
const lineaTest: CreateObjeniousLineDTO = {
simId: 1234,
iccid: "9999999999999",
msisdn: "34654674732",
imei: "219789481293",
imeiChangeDate: new Date(),
offerCode: "SAVEFAMILY1",
status: "ACTIVATED",
preactivationDate: new Date(),
activationDate: new Date(),
commercialStatus: "test",
commercialStatusDate: new Date(),
billingStatus: "test",
billingStatusChangeDate: new Date(),
billingActivationDate: new Date(),
createDate: new Date(),
raw: { test: "test" } as any // Para este test no hace falta
}
// Clean up before and after tests to ensure isolation
const cleanup = async () => {
await pgClient.query("DELETE FROM objenious_lines WHERE simId = 1234");
};
before(async () => {
await cleanup()
})
after(async () => {
await cleanup()
})
test("Should insert new line", async () => {
const res = await lineRepository.insertOrUpdate(lineaTest)
assert.ok(res != undefined, "The line wasn't created")
})
test("Should not update a line if the hash is the same", async () => {
const res = await lineRepository.insertOrUpdate(lineaTest)
assert.ok(res == undefined, "The line have been updated")
})
test("Should update a line if the hash changes", async () => {
const updated = structuredClone(lineaTest)
lineaTest.billingActivationDate = new Date()
const res = await lineRepository.insertOrUpdate(lineaTest)
assert.ok(res != undefined, "The line have been updated")
})
})

View File

@@ -0,0 +1,112 @@
/**
* Repositorio para el volcado de lineas de objenious en intranet
* solo para uso en el volcado.
*/
import { createHash } from "node:crypto";
import { PoolClient } from "pg";
import { CreateObjeniousLineDTO } from "sim-shared/domain/objeniousLine.js";
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
export class ObjeniousLinesRepository {
constructor(
private pgClient: PgClient
) {
}
private generateLineHash(data: CreateObjeniousLineDTO) {
try {
const lineStr = JSON.stringify(data)
const hash = createHash("sha256").update(lineStr).digest("base64url")
return hash
} catch (e) {
console.error("[x] Error generando el hash de la linea", data)
return undefined
}
}
public async insertOrUpdate(data: CreateObjeniousLineDTO) {
const query = `
INSERT INTO objenious_lines (
simId,
iccid,
msisdn,
imei,
imeiChangeDate,
offerCode,
status,
preactivationDate,
activationDate,
commercialStatus,
commercialStatusDate,
billingStatus,
billingStatusChangeDate,
billingActivationDate,
createDate,
raw,
hash
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17
)
ON CONFLICT (simId)
DO UPDATE SET
iccid = EXCLUDED.iccid,
msisdn = EXCLUDED.msisdn,
imei = EXCLUDED.imei,
imeiChangeDate = EXCLUDED.imeiChangeDate,
offerCode = EXCLUDED.offerCode,
status = EXCLUDED.status,
preactivationDate = EXCLUDED.preactivationDate,
activationDate = EXCLUDED.activationDate,
commercialStatus = EXCLUDED.commercialStatus,
commercialStatusDate = EXCLUDED.commercialStatusDate,
billingStatus = EXCLUDED.billingStatus,
billingStatusChangeDate = EXCLUDED.billingStatusChangeDate,
billingActivationDate = EXCLUDED.billingActivationDate,
raw = EXCLUDED.raw,
hash = EXCLUDED.hash
WHERE objenious_lines.hash IS DISTINCT FROM EXCLUDED.hash
RETURNING id;
`;
const lineHash = this.generateLineHash(data)
if (lineHash == undefined) {
console.error("[x] Ignorando linea ", data)
return;
}
const values = [
data.simId,
data.iccid,
data.msisdn,
data.imei,
data.imeiChangeDate,
data.offerCode,
data.status,
data.preactivationDate,
data.activationDate,
data.commercialStatus,
data.commercialStatusDate,
data.billingStatus,
data.billingStatusChangeDate,
data.billingActivationDate,
data.createDate || new Date(), // Default a ahora si no viene
JSON.stringify(data.raw), // El driver de pg requiere string o el objeto directo para JSONB
lineHash
];
let client: PoolClient | undefined = undefined;
try {
client = await this.pgClient.connect();
const res = await client.query<{ id: number }>(query, values);
return res.rows[0];
} catch (err) {
console.error('Error en la inserción:', err);
throw err;
} finally {
if (client != undefined) {
client.release()
}
}
}
}

View File

@@ -5,20 +5,6 @@
"description": "", "description": "",
"main": "index.ts", "main": "index.ts",
"imports": { "imports": {
"#config/*.js": {
"types": "./config/*.ts",
"default": "./config/*.js"
},
"#config/*": {
"types": "./config/*.ts",
"default": "./config/*.js"
},
"#shared/*.js": {
"default": "../sim-shared/*.js"
},
"#shared/*": {
"default": "../sim-shared/*.js"
},
"#adapters/*.js": { "#adapters/*.js": {
"types": "./infrastructure/*.ts", "types": "./infrastructure/*.ts",
"default": "./infrastructure/*.js" "default": "./infrastructure/*.js"
@@ -45,7 +31,7 @@
} }
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "node --import tsx --test ./**/*.test.ts",
"build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-objenious-cron/", "build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-objenious-cron/",
"dev": "tsx watch index.ts", "dev": "tsx watch index.ts",
"start": "node ../../dist/packages/sim-objenious-cron/index.js" "start": "node ../../dist/packages/sim-objenious-cron/index.js"

View File

@@ -1,4 +1,4 @@
import { env } from "#config/env/index.js"; import { env } from "../config/env/index.js";
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"; import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
import axios from "axios"; import axios from "axios";
import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js"; import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js";
@@ -16,6 +16,7 @@ export class CheckObjeniousRequests {
* TODO: meter a una funcion a parte task con los 3 pasos * TODO: meter a una funcion a parte task con los 3 pasos
*/ */
public async getPendingOperations() { public async getPendingOperations() {
console.log("[i] Inicio revision de peticiones")
// 1. Se obtienen todas las operaciones pendientes de la BDD // 1. Se obtienen todas las operaciones pendientes de la BDD
const pendingOperations = await this.operationsRepository.getPendingOperations() const pendingOperations = await this.operationsRepository.getPendingOperations()
@@ -49,11 +50,14 @@ export class CheckObjeniousRequests {
console.log("[cron] Solicitando status para", merged.map(e => e.id)) console.log("[cron] Solicitando status para", merged.map(e => e.id))
const result = await this.getMassActionsStatus(merged) const result = await this.getMassActionsStatus(merged)
console.log("[o] Revisión de eventos completa")
} }
/** /**
* Para una lista de operaciones **con mass_action_id** se comprueba si han tenido alguna actualizacion * Para una lista de operaciones **con mass_action_id** se comprueba si han tenido alguna actualizacion
* Devuelve el numero de operaciones comprobadas. * Devuelve el numero de operaciones comprobadas.
* TODO: Esto va en un repositorio
*/ */
private async getMassActionsStatus(requestList: ObjeniousOperation[]) { private async getMassActionsStatus(requestList: ObjeniousOperation[]) {
if (requestList.length == 0) return 0; if (requestList.length == 0) return 0;

View File

@@ -0,0 +1,133 @@
import assert from "node:assert";
import { lineToCreateLineDto, ObjeniousLine, ObjeniousLineResponse } from "sim-shared/domain/objeniousLine.js";
import { tryCatch, Result } from "sim-shared/domain/Result.js";
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js";
import { AxiosResponse } from "axios";
import { constants } from "node:buffer";
const MAX_PAGE_SIZE = 100
export class TaskVolcadoLineas {
constructor(
private readonly httpClient: HttpClient,
private readonly linesRepository: ObjeniousLinesRepository,
) {
}
/**
* Mover al repo
*/
private async * getLinesByStatus(args?: {
pageSize?: number,
pageNumber?: number,
status?: string
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
const path = "/lines"
const pageSize = args?.pageSize ?? MAX_PAGE_SIZE;
let currentPage = args?.pageNumber ?? 0;
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
const params: Record<string, string | number> = {}
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
if (args?.status != undefined) params["simStatus"] = args.status
params["pageSize"] = pageSize
params["pageNumber"] = page
console.log("Params", params)
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.httpClient.client.get(path, {
params: params
}))
if (nextPage.error != undefined) {
console.error(nextPage.error.msg)
return {
error: nextPage.error.msg.message
}
}
// Se aumenta para la siguiente ejecucion
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
totalPages = nextPage.data.data.totalPages
return {
data: nextPage.data.data.content
}
}
// El inicio se ejecuta siempre
const lines = await loadNextLine(currentPage)
if (lines.error != undefined) {
console.error("[x] Error obteniendo las lineas, cancelando operación");
return {
error: "Error cargando lineas"
}
}
currentPage++;
yield {
data: lines.data
}
// Copia para evitar bucles infinitos por error de la api
const maxPages = totalPages
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
console.log("maxPages", maxPages)
for (let i = currentPage; i < maxPages!; i++) {
console.log("Bucle i:", i, "page: ", currentPage)
yield await loadNextLine(currentPage);
currentPage++;
}
return {
data: []
}
}
private async saveLines(lines: ObjeniousLine[]) {
const linesToCreate = lines.map(lineToCreateLineDto)
let created: number[] = []
for (const line of linesToCreate) {
// Si es lento pasar a Promise.all
const res = await this.linesRepository.insertOrUpdate(line)
if (res?.id != undefined)
created.push(res.id)
}
}
public async loadLines() {
console.log("[i] Iniciando task de volcado de lineas de Objenious")
// Carga todas las lineas en memoria, hay que comprobar que no se gaste demasiada
const linesIterator = this.getLinesByStatus()
let lines = await linesIterator.next()
if (lines.value.error != undefined || lines.value.data == undefined) {
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
return;
}
await this.saveLines(lines.value.data)
while (!lines.done) {
console.log()
lines = await linesIterator.next()
if (lines.value.error != undefined || lines.value.data == undefined) {
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
return;
}
await this.saveLines(lines.value.data)
}
console.log("[i] Terminado task de volcado de lineas de Objenious")
}
}

View File

@@ -0,0 +1,144 @@
export type ObjeniousLineResponse = {
content: ObjeniousLine[],
offset: number,
pageNumber: number,
pageSize: number,
paged: boolean,
totalPages: number,
totalElements: number
}
export type ObjeniousLine = {
identifier: {
simId: number,
iccid: string,
imsi: string,
msisdn: string,
amsisdn?: string,
imei: string
},
simCardType: {
code: string,
description: string
},
device: {
imei: string,
imeiChangeDate: string, //Fecha iso
deviceReference?: string | null,
manufacturer?: string | null,
},
customerAccount: {
code: string,
label: string,
address: {
address1: string,
address2: string,
address3: string,
zipCode: string,
city: string,
country: string,
state?: string | null
}
},
offer: {
code: string,
description: string,
},
party: {
name: string,
code: string,
contractReference: string,
partyType: string,
},
lineCustomFields: {
custom1: {
label: string | null,
value: string | null
},
custom2: {
label: string | null,
value: string | null
},
custom3: {
label: string | null,
value: string | null
},
custom4: {
label: string | null,
value: string | null
},
custom5: {
label: string | null,
value: string | null
},
custom6: {
label: string | null,
value: string | null
}
},
status: {
status: string,
preactivationDate: string | null, //"2026-03-17",
activationDate: string | null, //"2026-03-17T11:04:11.408+00:00",
commercialStatus: string, //"test",
commercialStatusDate: string, //"2026-03-17T11:41:01.493+00:00",
networkStatus: string, // "ACTIVATED",
billingStatus: string, //"TEST",
billingStatusChangeDate: string | null, // "2026-03-17T11:01:00.276+00:00",
billingActivationDate: string | null //,
createdDate: string | null,//"2026-01-30T01:50:02.060+00:00"
},
services: string | null
};
export type ObjeniousLineDb = {
id: number;
simId?: number;
iccid: string;
msisdn?: string;
imei?: string;
imeiChangeDate?: Date;
offerCode?: string;
status?: string;
preactivationDate?: Date | null;
activationDate?: Date | null;
commercialStatus?: string;
commercialStatusDate?: Date | null;
billingStatus?: string;
billingStatusChangeDate?: Date | null;
billingActivationDate?: Date | null;
createDate?: Date | null;
raw: ObjeniousLine;
}
// DTO para inserción (omite el ID autogenerado)
export type CreateObjeniousLineDTO = Omit<ObjeniousLineDb, 'id'>;
export function lineToCreateLineDto(line: ObjeniousLine): CreateObjeniousLineDTO {
const dateOrNull = (data: string | null) => {
if (data == null) return null;
return new Date(data)
}
const transformed: CreateObjeniousLineDTO = {
simId: line.identifier.simId,
iccid: line.identifier.iccid,
msisdn: line.identifier.msisdn,
imei: line.identifier.imei,
imeiChangeDate: new Date(line.device.imeiChangeDate),
offerCode: line.offer.code,
status: line.status.status,
preactivationDate: dateOrNull(line.status.preactivationDate),
activationDate: dateOrNull(line.status.activationDate),
commercialStatus: line.status.commercialStatus,
commercialStatusDate: dateOrNull(line.status.commercialStatusDate),
billingStatus: line.status.billingStatus,
billingStatusChangeDate: dateOrNull(line.status.activationDate),
billingActivationDate: dateOrNull(line.status.activationDate),
createDate: dateOrNull(line.status.activationDate),
raw: line
}
return transformed;
}

View File

@@ -1,12 +1,3 @@
#/bin/bash #/bin/bash
rm deployment/database/init.sql
# init sql debe juntar todos los scripts de "base" (sin contar migraciones)
cat deployment/database/base/*.sql >deployment/database/init.sql
#cp deployment/database/esquema_final* deployment/database/init.sql
# compatibilidad con postgresql < 17
sed -i '/\\restrict/d' deployment/database/init.sql
sed -i '/\\unrestrict/d' deployment/database/init.sql
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ up --watch docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ up --watch

View File

@@ -452,6 +452,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@sf-alvar/db-migrate@npm:1.0.3":
version: 1.0.3
resolution: "@sf-alvar/db-migrate@npm:1.0.3::__archiveUrl=https%3A%2F%2Fgit.savefamilygps.net%2Fapi%2Fpackages%2FSaveFamily%2Fnpm%2F%2540sf-alvar%252Fdb-migrate%2F-%2F1.0.3%2Fdb-migrate-1.0.3.tgz"
dependencies:
pg: "npm:^8.18.0"
yargs: "npm:^18.0.0"
bin:
db-migrate: lib/index.js
checksum: 10/2b5745a5ce60456fc7fee1e6a8580978a520fedd8abbbc695557847cdf2b36aa5e1d795721ad35bc151fc9373dfa023bde73d6f43ba412b17293a1822c09fe6b
languageName: node
linkType: hard
"@standard-schema/spec@npm:^1.0.0": "@standard-schema/spec@npm:^1.0.0":
version: 1.1.0 version: 1.1.0
resolution: "@standard-schema/spec@npm:1.1.0" resolution: "@standard-schema/spec@npm:1.1.0"
@@ -1142,18 +1154,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"db-migrate@http://gitea:3000/alvarsanmartin/herramienta-migracion.git":
version: 1.1.0
resolution: "db-migrate@http://gitea:3000/alvarsanmartin/herramienta-migracion.git#commit=f84d68ba79161b9b06b747919979db00aac34b49"
dependencies:
pg: "npm:^8.18.0"
yargs: "npm:^18.0.0"
bin:
db-migrate: ./lib/index.js
checksum: 10/2468cfd14a5f218845f5437f530a68993a51b3998cdd9d0c7f28cdb810314200c471debac9ca19c34d4978907b7c4ced5c95e777eebd40c3baa795ad945d8892
languageName: node
linkType: hard
"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3": "debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3":
version: 4.4.3 version: 4.4.3
resolution: "debug@npm:4.4.3" resolution: "debug@npm:4.4.3"
@@ -2372,13 +2372,13 @@ __metadata:
linkType: hard linkType: hard
"postcss@npm:^8.5.6": "postcss@npm:^8.5.6":
version: 8.5.6 version: 8.5.8
resolution: "postcss@npm:8.5.6" resolution: "postcss@npm:8.5.8"
dependencies: dependencies:
nanoid: "npm:^3.3.11" nanoid: "npm:^3.3.11"
picocolors: "npm:^1.1.1" picocolors: "npm:^1.1.1"
source-map-js: "npm:^1.2.1" source-map-js: "npm:^1.2.1"
checksum: 10/9e4fbe97574091e9736d0e82a591e29aa100a0bf60276a926308f8c57249698935f35c5d2f4e80de778d0cbb8dcffab4f383d85fd50c5649aca421c3df729b86 checksum: 10/cbacbfd7f767e2c820d4bf09a3a744834dd7d14f69ff08d1f57b1a7defce9ae5efcf31981890d9697a972a64e9965de677932ef28e4c8ba23a87aad45b82c459
languageName: node languageName: node
linkType: hard linkType: hard
@@ -2856,6 +2856,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "sim-eventos@workspace:." resolution: "sim-eventos@workspace:."
dependencies: dependencies:
"@sf-alvar/db-migrate": "npm:1.0.3"
"@tsconfig/node22": "npm:^22.0.5" "@tsconfig/node22": "npm:^22.0.5"
"@types/amqplib": "npm:^0.10.8" "@types/amqplib": "npm:^0.10.8"
"@types/cors": "npm:^2.8.19" "@types/cors": "npm:^2.8.19"
@@ -2868,7 +2869,6 @@ __metadata:
axios: "npm:^1.13.3" axios: "npm:^1.13.3"
concurrently: "npm:^9.2.1" concurrently: "npm:^9.2.1"
cors: "npm:^2.8.5" cors: "npm:^2.8.5"
db-migrate: "http://gitea:3000/alvarsanmartin/herramienta-migracion.git"
dotenv: "npm:^17.2.3" dotenv: "npm:^17.2.3"
express: "npm:^5.2.1" express: "npm:^5.2.1"
pg: "npm:^8.18.0" pg: "npm:^8.18.0"