Versión: 1.0
Fecha: 27 de Diciembre, 2025
Estado: OPERATIVO - Workflow Diario
Arquitecto: Carlos Alberto Torres Camargo
Este documento describe el flujo de trabajo día a día de un desarrollador en el equipo Nebula, desde la asignación de un ticket hasta el deploy en producción.
Ejemplo Práctico: Crear entidad Tercero (clientes/proveedores) en el módulo de Contabilidad.
Herramienta: JIRA / Azure DevOps
Ticket Ejemplo:
ID: NEB-158
Título: Implementar CRUD de Terceros (Clientes/Proveedores)
Tipo: Feature
Sprint: Sprint 1.4
Asignado a: Juan Pérez (Backend Senior)
Story Points: 8
Descripción:
Como contador, necesito registrar terceros (clientes, proveedores, empleados)
con su información fiscal (NIT, nombre, dirección) para poder crear
asientos contables y facturas.
Criterios de Aceptación:
- CRUD completo de terceros
- Validación de NIT con dígito de verificación
- Tipos de tercero: CLIENTE, PROVEEDOR, EMPLEADO, OTRO
- Búsqueda por NIT, nombre
- Paginación y ordenamiento
- Tests unitarios >80% cobertura
- API documentada en Swagger
Dependencias:
- CentricaModel debe tener TerceroDto creado
CentricaModel# Clonar o actualizar CentricaModel
cd ~/workspace
git clone git@github.com:centrica/CentricaModel.git
cd CentricaModel
# Checkout develop
git checkout develop
git pull origin develop
# Crear feature branch
git checkout -b feature/NEB-158-dto-tercero
Archivo: src/main/java/com/centrica/nebula/model/accounting/TerceroDto.java
package com.centrica.nebula.model.accounting;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "DTO para Tercero (Cliente/Proveedor/Empleado)")
public class TerceroDto {
@Schema(description = "ID único del tercero", example = "123")
private Long id;
@NotBlank(message = "El tipo de identificación es obligatorio")
@Schema(description = "Tipo de identificación", example = "NIT",
allowableValues = {"NIT", "CC", "CE", "PASAPORTE"})
private String tipoIdentificacion;
@NotBlank(message = "El número de identificación es obligatorio")
@Size(max = 20, message = "El NIT no puede exceder 20 caracteres")
@Schema(description = "Número de identificación (NIT sin DV)", example = "900123456")
private String numeroIdentificacion;
@Schema(description = "Dígito de verificación", example = "7")
private String digitoVerificacion;
@NotBlank(message = "El nombre es obligatorio")
@Size(max = 200, message = "El nombre no puede exceder 200 caracteres")
@Schema(description = "Nombre o razón social", example = "Acme Corp S.A.S.")
private String nombre;
@Size(max = 100, message = "El nombre comercial no puede exceder 100 caracteres")
@Schema(description = "Nombre comercial", example = "Acme")
private String nombreComercial;
@NotNull(message = "El tipo de tercero es obligatorio")
@Schema(description = "Tipo de tercero", example = "CLIENTE",
allowableValues = {"CLIENTE", "PROVEEDOR", "EMPLEADO", "OTRO"})
private String tipoTercero;
@Email(message = "Email inválido")
@Size(max = 100)
@Schema(description = "Email de contacto", example = "contacto@acme.com")
private String email;
@Size(max = 20)
@Schema(description = "Teléfono", example = "+57 4 3001234")
private String telefono;
@Size(max = 200)
@Schema(description = "Dirección", example = "Calle 50 #45-30")
private String direccion;
@Size(max = 100)
@Schema(description = "Ciudad", example = "Medellín")
private String ciudad;
@Size(max = 100)
@Schema(description = "Departamento/Estado", example = "Antioquia")
private String departamento;
@Size(max = 20)
@Schema(description = "Código postal", example = "050001")
private String codigoPostal;
@Size(max = 50)
@Schema(description = "País", example = "Colombia")
private String pais;
@Schema(description = "Tercero activo", example = "true")
private Boolean activo;
@Schema(description = "Observaciones", example = "Cliente VIP")
private String observaciones;
// Campos de auditoría
@Schema(description = "Fecha de creación")
private LocalDate fechaCreacion;
@Schema(description = "Usuario que creó el registro")
private String creadoPor;
}
git add src/main/java/com/centrica/nebula/model/accounting/TerceroDto.java
git commit -m "feat(accounting): agregar TerceroDto
- Crear DTO para gestión de terceros
- Validaciones con Jakarta Bean Validation
- Documentación Swagger/OpenAPI
- Soporte para tipos: CLIENTE, PROVEEDOR, EMPLEADO
Refs: NEB-158"
git push -u origin feature/NEB-158-dto-tercero
GitHub/GitLab UI:
# [NEB-158] Agregar TerceroDto a CentricaModel
## Descripción
Agrega DTO para gestión de terceros (clientes, proveedores, empleados) en módulo de contabilidad.
## Archivos Modificados
- `TerceroDto.java` - DTO con validaciones y Swagger docs
## Checklist
- [x] Validaciones Jakarta Bean Validation
- [x] Documentación Swagger completa
- [x] Naming conventions seguidas
- [x] Sin dependencias de terceros no aprobadas
## Revisores
@arquitecto @backend-senior-2
Refs: #NEB-158
Arquitecto revisa → Aprueba → Merge a develop
Jenkins Pipeline ejecuta:
mvn clean compilemvn test (si aplica)mvn deploy
1.1.0-SNAPSHOT (auto-incrementada)http://${NEXUS_HOST}/repository/maven-snapshots/Output:
[INFO] Installing com.centrica.nebula:CentricaModel:1.1.0-SNAPSHOT
[INFO] Uploading to nexus-snapshots: http://nexus.centrica.internal/...
[INFO] BUILD SUCCESS
cd ~/workspace
git clone git@github.com:centrica/nebula-accounting.git
cd nebula-accounting
git checkout develop
git pull origin develop
git checkout -b feature/NEB-158-tercero-crud
pom.xml<!-- pom.xml - Actualizar versión de CentricaModel -->
<dependency>
<groupId>com.centrica.nebula</groupId>
<artifactId>CentricaModel</artifactId>
<version>1.1.0-SNAPSHOT</version> <!-- Era 1.0.0-SNAPSHOT -->
</dependency>
mvn clean install -U
# -U fuerza actualización de SNAPSHOTs desde Nexus
Verificar:
mvn dependency:tree | grep CentricaModel
# [INFO] +- com.centrica.nebula:CentricaModel:jar:1.1.0-SNAPSHOT:compile
Archivo: src/main/java/com/centrica/nebula/accounting/v1/entity/Tercero.java
package com.centrica.nebula.accounting.v1.entity;
import com.catcsoft.simappe.commons.entity.BusinessBaseEntity;
import com.catcsoft.simappe.commons.entity.LongId;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "tercero", schema = "contabilidad",
indexes = {
@Index(name = "idx_tercero_nit", columnList = "numero_identificacion"),
@Index(name = "idx_tercero_tipo", columnList = "tipo_tercero"),
@Index(name = "idx_tercero_nombre", columnList = "nombre")
},
uniqueConstraints = {
@UniqueConstraint(name = "uk_tercero_nit",
columnNames = {"numero_identificacion", "tipo_identificacion"})
}
)
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Tercero extends BusinessBaseEntity<LongId> {
@Column(name = "tipo_identificacion", length = 20, nullable = false)
private String tipoIdentificacion;
@Column(name = "numero_identificacion", length = 20, nullable = false)
private String numeroIdentificacion;
@Column(name = "digito_verificacion", length = 1)
private String digitoVerificacion;
@Column(name = "nombre", length = 200, nullable = false)
private String nombre;
@Column(name = "nombre_comercial", length = 100)
private String nombreComercial;
@Column(name = "tipo_tercero", length = 20, nullable = false)
private String tipoTercero;
@Column(name = "email", length = 100)
private String email;
@Column(name = "telefono", length = 20)
private String telefono;
@Column(name = "direccion", length = 200)
private String direccion;
@Column(name = "ciudad", length = 100)
private String ciudad;
@Column(name = "departamento", length = 100)
private String departamento;
@Column(name = "codigo_postal", length = 20)
private String codigoPostal;
@Column(name = "pais", length = 50)
private String pais;
@Column(name = "activo", nullable = false)
private Boolean activo = true;
@Column(name = "observaciones", length = 500)
private String observaciones;
}
Archivo: src/main/java/com/centrica/nebula/accounting/v1/repository/TerceroRepository.java
package com.centrica.nebula.accounting.v1.repository;
import com.catcsoft.simappe.commons.entity.LongId;
import com.catcsoft.simappe.commons.repository.SimappeRepository;
import com.centrica.nebula.accounting.v1.entity.Tercero;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface TerceroRepository extends SimappeRepository<Tercero, LongId> {
/**
* Buscar tercero por número de identificación
*/
Optional<Tercero> findByNumeroIdentificacionAndTipoIdentificacion(
String numeroIdentificacion,
String tipoIdentificacion
);
/**
* Buscar terceros por tipo (CLIENTE, PROVEEDOR, etc.)
*/
List<Tercero> findByTipoTercero(String tipoTercero);
/**
* Búsqueda por nombre (case-insensitive, like)
*/
@Query("SELECT t FROM Tercero t WHERE LOWER(t.nombre) LIKE LOWER(CONCAT('%', :nombre, '%'))")
List<Tercero> searchByNombre(@Param("nombre") String nombre);
/**
* Verificar si existe tercero con NIT
*/
boolean existsByNumeroIdentificacionAndTipoIdentificacion(
String numeroIdentificacion,
String tipoIdentificacion
);
}
Archivo: src/main/java/com/centrica/nebula/accounting/v1/component/TerceroComponent.java
package com.centrica.nebula.accounting.v1.component;
import com.catcsoft.simappe.commons.annotation.ConnectionContext;
import com.catcsoft.simappe.commons.annotation.EntityIdSupport;
import com.catcsoft.simappe.commons.entity.LongId;
import com.catcsoft.simappe.commons.exception.SimappeException;
import com.catcsoft.simappe.commons.mapper.SimappeModelMapper;
import com.catcsoft.simappe.commons.service.SimappeService;
import com.catcsoft.simappe.commons.service.CrudHtppServletRequestComponent;
import com.centrica.nebula.accounting.v1.entity.Tercero;
import com.centrica.nebula.accounting.v1.repository.TerceroRepository;
import com.centrica.nebula.model.accounting.TerceroDto;
import io.micrometer.observation.annotation.Observed;
import io.micrometer.core.annotation.Timed;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Component
@ConnectionContext // Activa multi-tenancy
@EntityIdSupport
@RequiredArgsConstructor
@Slf4j
public class TerceroComponent extends SimappeService<TerceroDto, Long, Tercero, TerceroRepository>
implements CrudHtppServletRequestComponent<TerceroDto, Long> {
private final TerceroRepository repository;
private final SimappeModelMapper mapper;
@Override
@Transactional(readOnly = true)
@Observed(name = "tercero-read")
@Timed(value = "tercero-read")
public List<TerceroDto> read(HttpServletRequest request) {
log.info("Leyendo todos los terceros");
return repository.findAll().stream()
.map(entity -> mapper.map(entity, TerceroDto.class))
.collect(Collectors.toList());
}
@Override
@Transactional
@Observed(name = "tercero-create")
@Timed(value = "tercero-create")
public TerceroDto create(TerceroDto dto, HttpServletRequest request) {
log.info("Creando tercero: {}", dto.getNombre());
// Validación de negocio: NIT duplicado
if (repository.existsByNumeroIdentificacionAndTipoIdentificacion(
dto.getNumeroIdentificacion(),
dto.getTipoIdentificacion())) {
throw new SimappeException(
"Ya existe un tercero con este número de identificación: "
+ dto.getNumeroIdentificacion()
);
}
// Validación: Calcular y validar dígito de verificación si es NIT
if ("NIT".equals(dto.getTipoIdentificacion())) {
String dvCalculado = calcularDigitoVerificacion(dto.getNumeroIdentificacion());
if (!dvCalculado.equals(dto.getDigitoVerificacion())) {
throw new SimappeException(
"Dígito de verificación inválido. Calculado: " + dvCalculado
+ ", Proporcionado: " + dto.getDigitoVerificacion()
);
}
}
// Mapeo DTO → Entity
Tercero entity = mapper.map(dto, Tercero.class);
// Persistir
Tercero saved = repository.save(entity);
log.info("Tercero creado con ID: {}", saved.getId());
// Auditoría (opcional, vía Kafka)
// auditLogService.sendMessage("TERCERO_CREATED", saved);
return mapper.map(saved, TerceroDto.class);
}
@Override
@Transactional
@Observed(name = "tercero-update")
@Timed(value = "tercero-update")
public TerceroDto update(Long id, TerceroDto dto, HttpServletRequest request) {
log.info("Actualizando tercero ID: {}", id);
Tercero existing = repository.findById(new LongId(id))
.orElseThrow(() -> new SimappeException("Tercero no encontrado: " + id));
// Actualizar campos (no sobreescribir ID ni campos de auditoría)
existing.setNombre(dto.getNombre());
existing.setEmail(dto.getEmail());
existing.setTelefono(dto.getTelefono());
existing.setDireccion(dto.getDireccion());
existing.setCiudad(dto.getCiudad());
existing.setActivo(dto.getActivo());
// ... otros campos
Tercero updated = repository.save(existing);
return mapper.map(updated, TerceroDto.class);
}
@Override
@Transactional
@Observed(name = "tercero-delete")
@Timed(value = "tercero-delete")
public void delete(Long id, HttpServletRequest request) {
log.info("Eliminando tercero ID: {}", id);
if (!repository.existsById(new LongId(id))) {
throw new SimappeException("Tercero no encontrado: " + id);
}
// Soft delete recomendado
Tercero tercero = repository.findById(new LongId(id)).get();
tercero.setActivo(false);
repository.save(tercero);
// O hard delete si se requiere
// repository.deleteById(new LongId(id));
}
/**
* Algoritmo de cálculo de dígito de verificación para NIT en Colombia
*/
private String calcularDigitoVerificacion(String nit) {
int[] primos = {3, 7, 13, 17, 19, 23, 29, 37, 41, 43, 47, 53, 59, 67, 71};
int suma = 0;
for (int i = 0; i < nit.length(); i++) {
int digito = Character.getNumericValue(nit.charAt(nit.length() - 1 - i));
suma += digito * primos[i];
}
int residuo = suma % 11;
int dv = (residuo > 1) ? 11 - residuo : residuo;
return String.valueOf(dv);
}
}
Archivo: src/main/java/com/centrica/nebula/accounting/v1/service/TerceroService.java
package com.centrica.nebula.accounting.v1.service;
import com.centrica.nebula.accounting.v1.component.TerceroComponent;
import com.centrica.nebula.model.accounting.TerceroDto;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class TerceroService {
private final TerceroComponent component;
public List<TerceroDto> read(HttpServletRequest request) {
return component.read(request);
}
public TerceroDto create(TerceroDto dto, HttpServletRequest request) {
return component.create(dto, request);
}
public TerceroDto update(Long id, TerceroDto dto, HttpServletRequest request) {
return component.update(id, dto, request);
}
public void delete(Long id, HttpServletRequest request) {
component.delete(id, request);
}
public TerceroDto getById(Long id, HttpServletRequest request) {
return component.get(id, request);
}
}
Archivo: src/main/java/com/centrica/nebula/accounting/v1/controller/TerceroController.java
package com.centrica.nebula.accounting.v1.controller;
import com.centrica.nebula.accounting.v1.service.TerceroService;
import com.centrica.nebula.model.accounting.TerceroDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/accounting/terceros")
@RequiredArgsConstructor
@Tag(name = "Terceros", description = "Gestión de Terceros (Clientes/Proveedores/Empleados)")
public class TerceroController {
private final TerceroService service;
@GetMapping("/")
@Operation(summary = "Health check", description = "Verifica disponibilidad del servicio")
public ResponseEntity<String> healthCheck() {
return ResponseEntity.ok("Terceros API disponible");
}
@GetMapping("/read")
@Operation(summary = "Listar terceros", description = "Obtiene lista completa de terceros")
public ResponseEntity<List<TerceroDto>> read(HttpServletRequest request) {
return ResponseEntity.ok(service.read(request));
}
@GetMapping("/get")
@Operation(summary = "Obtener tercero por ID")
public ResponseEntity<TerceroDto> get(
@RequestParam Long id,
HttpServletRequest request
) {
return ResponseEntity.ok(service.getById(id, request));
}
@PostMapping("/create")
@Operation(summary = "Crear tercero")
public ResponseEntity<TerceroDto> create(
@Valid @RequestBody TerceroDto dto,
HttpServletRequest request
) {
return ResponseEntity.ok(service.create(dto, request));
}
@PutMapping("/update")
@Operation(summary = "Actualizar tercero")
public ResponseEntity<TerceroDto> update(
@RequestParam Long id,
@Valid @RequestBody TerceroDto dto,
HttpServletRequest request
) {
return ResponseEntity.ok(service.update(id, dto, request));
}
@DeleteMapping("/delete")
@Operation(summary = "Eliminar tercero")
public ResponseEntity<Void> delete(
@RequestParam Long id,
HttpServletRequest request
) {
service.delete(id, request);
return ResponseEntity.noContent().build();
}
}
Archivo: src/test/java/com/centrica/nebula/accounting/v1/component/TerceroComponentTest.java
package com.centrica.nebula.accounting.v1.component;
import com.catcsoft.simappe.commons.entity.LongId;
import com.catcsoft.simappe.commons.exception.SimappeException;
import com.catcsoft.simappe.commons.mapper.SimappeModelMapper;
import com.centrica.nebula.accounting.v1.entity.Tercero;
import com.centrica.nebula.accounting.v1.repository.TerceroRepository;
import com.centrica.nebula.model.accounting.TerceroDto;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class TerceroComponentTest {
@Mock
private TerceroRepository repository;
@Mock
private SimappeModelMapper mapper;
@InjectMocks
private TerceroComponent component;
private TerceroDto dtoMock;
private Tercero entityMock;
@BeforeEach
void setUp() {
dtoMock = TerceroDto.builder()
.tipoIdentificacion("NIT")
.numeroIdentificacion("900123456")
.digitoVerificacion("7")
.nombre("Acme Corp S.A.S.")
.tipoTercero("CLIENTE")
.email("contacto@acme.com")
.activo(true)
.build();
entityMock = new Tercero();
entityMock.setId(new LongId(1L));
entityMock.setNumeroIdentificacion("900123456");
entityMock.setNombre("Acme Corp S.A.S.");
}
@Test
void create_Success() {
// Arrange
when(repository.existsByNumeroIdentificacionAndTipoIdentificacion(
anyString(), anyString())).thenReturn(false);
when(mapper.map(any(TerceroDto.class), eq(Tercero.class))).thenReturn(entityMock);
when(repository.save(any(Tercero.class))).thenReturn(entityMock);
when(mapper.map(any(Tercero.class), eq(TerceroDto.class))).thenReturn(dtoMock);
// Act
TerceroDto result = component.create(dtoMock, null);
// Assert
assertNotNull(result);
assertEquals("Acme Corp S.A.S.", result.getNombre());
verify(repository).save(any(Tercero.class));
}
@Test
void create_DuplicateNIT_ThrowsException() {
// Arrange
when(repository.existsByNumeroIdentificacionAndTipoIdentificacion(
anyString(), anyString())).thenReturn(true);
// Act & Assert
assertThrows(SimappeException.class, () -> {
component.create(dtoMock, null);
});
verify(repository, never()).save(any());
}
@Test
void create_InvalidDV_ThrowsException() {
// Arrange
dtoMock.setDigitoVerificacion("9"); // DV incorrecto (debería ser 7)
// Act & Assert
assertThrows(SimappeException.class, () -> {
component.create(dtoMock, null);
});
}
}
mvn test
# Output esperado:
# [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
# [INFO] BUILD SUCCESS
git add .
git commit -m "feat(accounting): implementar CRUD de Terceros
- Crear Entity Tercero con índices y constraints
- Crear TerceroRepository con queries custom
- Crear TerceroComponent con validación de NIT
- Crear TerceroService y TerceroController
- Tests unitarios con 92% cobertura
- Documentación Swagger/OpenAPI
Validaciones implementadas:
- NIT duplicado
- Cálculo de dígito de verificación
- Tipos de tercero: CLIENTE, PROVEEDOR, EMPLEADO
Refs: NEB-158"
git push -u origin feature/NEB-158-tercero-crud
# [NEB-158] Implementar CRUD de Terceros
## Descripción
Implementa gestión completa de terceros (clientes, proveedores, empleados) en módulo de contabilidad.
## Cambios
- Entity `Tercero` con validaciones JPA
- Repository con búsquedas por NIT, nombre, tipo
- Component con lógica de negocio y validaciones
- Service y Controller REST
- Tests unitarios (92% cobertura)
## Testing
- [x] Tests unitarios (Mockito) - 3 casos
- [x] Tests manuales con Postman
- [x] Validación de dígito de verificación probada
## Checklist
- [x] Código compila sin errores
- [x] Tests pasan (mvn test)
- [x] Cobertura >80% (JaCoCo)
- [x] SonarQube sin code smells críticos
- [x] Swagger UI documentado
- [x] Dependency de CentricaModel actualizada
## Screenshots
[Adjuntar Swagger UI de endpoints]
Refs: #NEB-158
Revisor (Backend Senior 2):
✅ Código limpio y siguiendo convenciones
✅ Validaciones de negocio correctas
✅ Tests completos
✅ Sin vulnerabilidades de seguridad
✅ Documentación clara
Merge a develop → CI/CD ejecuta:
# 1. Obtener JWT
POST http://dev-oauth2.centrica.internal/oauth/token
Body: grant_type=password&username=dev@test.com&password=dev123
# 2. Crear tercero
POST http://dev-accounting.centrica.internal/api/v1/accounting/terceros/create
Headers: Authorization: Bearer {JWT}
Body:
{
"tipoIdentificacion": "NIT",
"numeroIdentificacion": "900123456",
"digitoVerificacion": "7",
"nombre": "Acme Corp S.A.S.",
"tipoTercero": "CLIENTE",
"email": "contacto@acme.com",
"activo": true
}
# 3. Listar terceros
GET http://dev-accounting.centrica.internal/api/v1/accounting/terceros/read
Headers: Authorization: Bearer {JWT}
# Conectar a BD del tenant DEV
psql -h dev-postgres.internal -U nebula_dev -d nebula_dev_dev
# Verificar registro
SELECT * FROM contabilidad.tercero WHERE numero_identificacion = '900123456';
Síntoma: Crear tercero retorna 200 OK, pero no aparece en /read.
Diagnóstico:
// Agregar logs en TerceroComponent.create()
log.info("Tenant ID actual: {}", TenantContext.getCurrentTenant());
log.info("DataSource usado: {}", dataSource.getConnection().getMetaData().getURL());
Causas Posibles:
JWT sin claim sub correcto:
"sub": "clientId_environment""sub": "acme_dev"database_config_v2 no existe:
GET /api/v2/database-config/get?code=acme_devFalta anotación @ConnectionContext:
TerceroComponent tenga @ConnectionContextDataSource no se inicializó:
TenantDatabaseManager initialized for tenant: acme_devResumen del Workflow (NEB-158):
CentricaModel → PR → Merge → Deploy a Nexusnebula-accountingdevelop → Code Review → MergeTiempo Estimado: 2-3 días para feature de CRUD completo.
Documento Operativo - Flujo de Desarrollo
© 2025 CatcSoft - Medellín, Colombia
| Version | Fecha | Autor | Descripcion |
|---|---|---|---|
| 1.1.0 | 2026-03-04 | Carlos Torres | Revision, sanitizacion y publicacion en Wiki Arquitectura Centrica. |
| 1.0.0 | 2025-12-27 | Carlos Torres | Creacion del documento. |