Sistema NEXUS v3.4 migrado con: Estructura principal: - core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles) - core/catalog: Catalogo de funcionalidades reutilizables - shared/knowledge-base: Base de conocimiento compartida - devtools/scripts: Herramientas de desarrollo - control-plane/registries: Control de servicios y CI/CD - orchestration/: Configuracion de orquestacion de agentes Proyectos incluidos (11): - gamilit (submodule -> GitHub) - trading-platform (OrbiquanTIA) - erp-suite con 5 verticales: - erp-core, construccion, vidrio-templado - mecanicas-diesel, retail, clinicas - betting-analytics - inmobiliaria-analytics - platform_marketing_content - pos-micro, erp-basico Configuracion: - .gitignore completo para Node.js/Python/Docker - gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git) - Sistema de puertos estandarizado (3005-3199) Generated with NEXUS v3.4 Migration System EPIC-010: Configuracion Git y Repositorios
2.6 KiB
2.6 KiB
ET-QUA-003: Motor de Tickets
ID: ET-QUA-003 | Módulo: MAI-009
Schema
CREATE TYPE quality.ticket_priority AS ENUM ('urgent', 'high', 'medium', 'low');
CREATE TYPE quality.ticket_status AS ENUM ('created', 'assigned', 'in_progress', 'resolved', 'closed');
CREATE TABLE quality.post_sale_tickets (
id UUID PRIMARY KEY,
numero VARCHAR(50) UNIQUE,
housing_id UUID,
derechohabiente_id UUID,
category VARCHAR(50),
priority quality.ticket_priority,
description TEXT,
photos TEXT[],
status quality.ticket_status DEFAULT 'created',
created_at TIMESTAMPTZ DEFAULT NOW(),
sla_deadline TIMESTAMPTZ,
assigned_to UUID,
assigned_at TIMESTAMPTZ,
resolved_at TIMESTAMPTZ,
resolution_notes TEXT,
resolution_photos TEXT[],
satisfaction_rating INT, -- 1-5
closed_at TIMESTAMPTZ
);
Backend Service
@Injectable()
export class TicketService {
async create(dto: CreateTicketDto): Promise<PostSaleTicket> {
const priority = this.determinePriority(dto.category);
const slaDeadline = this.calculateSLADeadline(priority);
const numero = await this.getNextTicketNumber();
const ticket = this.ticketsRepo.create({ ...dto, numero, priority, slaDeadline });
await this.ticketsRepo.save(ticket);
// Asignación automática
await this.autoAssignTechnician(ticket);
return ticket;
}
private async autoAssignTechnician(ticket: PostSaleTicket): Promise<void> {
const technicians = await this.getTechniciansBy Specialty(ticket.category);
const available = technicians.filter(t => t.isAvailable && t.activeTickets < 5);
if (available.length === 0) {
await this.alertNoTechniciansAvailable(ticket);
return;
}
// Asignar al técnico con menor carga
const selected = available.sort((a, b) => a.activeTickets - b.activeTickets)[0];
await this.ticketsRepo.update(ticket.id, {
assignedTo: selected.id,
assignedAt: new Date(),
status: 'assigned'
});
await this.notifyTechnician(selected, ticket);
}
@Cron('0 */1 * * *')
async checkSLACompliance(): Promise<void> {
const atRisk = await this.ticketsRepo.find({
where: { status: In(['created', 'assigned', 'in_progress']) }
});
for (const ticket of atRisk) {
const elapsed = differenceInHours(new Date(), ticket.createdAt);
const slaHours = this.getSLAHours(ticket.priority);
const percentElapsed = (elapsed / slaHours) * 100;
if (percentElapsed > 80) {
await this.escalateTicket(ticket);
}
}
}
}
Generado: 2025-11-20