Initial commit

This commit is contained in:
Anibal Angulo
2026-02-18 19:29:54 +00:00
commit da95a64fb7
125 changed files with 8796 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.mapper.conversation;
import com.example.dto.dialogflow.conversation.ConversationEntryDTO;
import com.example.dto.dialogflow.conversation.ConversationMessageDTO;
import com.example.dto.dialogflow.conversation.MessageType;
import org.springframework.stereotype.Component;
@Component
public class ConversationEntryMapper {
public ConversationMessageDTO toConversationMessageDTO(ConversationEntryDTO entry) {
MessageType type = switch (entry.entity()) {
case USUARIO -> MessageType.USER;
case AGENTE -> MessageType.AGENT;
case SISTEMA -> MessageType.SYSTEM;
case LLM -> MessageType.LLM;
};
return new ConversationMessageDTO(
type,
entry.timestamp(),
entry.text(),
entry.parameters(),
entry.canal()
);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.mapper.conversation;
import com.google.cloud.Timestamp;
import com.example.dto.dialogflow.conversation.ConversationMessageDTO;
import com.example.dto.dialogflow.conversation.MessageType;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@Component
public class ConversationMessageMapper {
public Map<String, Object> toMap(ConversationMessageDTO message) {
Map<String, Object> map = new HashMap<>();
map.put("entidad", message.type().name());
map.put("tiempo", message.timestamp());
map.put("mensaje", message.text());
if (message.parameters() != null) {
map.put("parametros", message.parameters());
}
if (message.canal() != null) {
map.put("canal", message.canal());
}
return map;
}
public ConversationMessageDTO fromMap(Map<String, Object> map) {
Object timeObject = map.get("tiempo");
Instant timestamp = null;
if (timeObject instanceof Timestamp) {
timestamp = ((Timestamp) timeObject).toDate().toInstant();
} else if (timeObject instanceof Instant) {
timestamp = (Instant) timeObject;
}
return new ConversationMessageDTO(
MessageType.valueOf((String) map.get("entidad")),
timestamp,
(String) map.get("mensaje"),
(Map<String, Object>) map.get("parametros"),
(String) map.get("canal")
);
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.mapper.conversation;
import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
import com.example.dto.dialogflow.conversation.QueryInputDTO;
import com.example.util.ProtobufUtil;
import com.google.cloud.dialogflow.cx.v3.EventInput;
import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest;
import com.google.cloud.dialogflow.cx.v3.QueryInput;
import com.google.cloud.dialogflow.cx.v3.QueryParameters;
import com.google.cloud.dialogflow.cx.v3.TextInput;
import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects;
/**
* Spring component responsible for mapping a custom `DetectIntentRequestDTO`
* into a Dialogflow CX `DetectIntentRequest.Builder`. It handles the conversion
* of user text or event inputs and the serialization of custom session parameters,
* ensuring the data is in the correct Protobuf format for API communication.
*/
@Component
public class DialogflowRequestMapper {
private static final Logger logger = LoggerFactory.getLogger(DialogflowRequestMapper.class);
@org.springframework.beans.factory.annotation.Value("${dialogflow.default-language-code:es}")
String defaultLanguageCode;
public DetectIntentRequest.Builder mapToDetectIntentRequestBuilder(DetectIntentRequestDTO requestDto) {
Objects.requireNonNull(requestDto, "DetectIntentRequestDTO cannot be null for mapping.");
logger.debug(
"Building partial Dialogflow CX DetectIntentRequest Protobuf Builder from DTO (only QueryInput and QueryParams).");
QueryInput.Builder queryInputBuilder = QueryInput.newBuilder();
QueryInputDTO queryInputDTO = requestDto.queryInput();
String languageCodeToSet = (queryInputDTO.languageCode() != null
&& !queryInputDTO.languageCode().trim().isEmpty())
? queryInputDTO.languageCode()
: defaultLanguageCode;
queryInputBuilder.setLanguageCode(languageCodeToSet);
logger.debug("Setting languageCode for QueryInput to: {}", languageCodeToSet);
if (queryInputDTO.text() != null && queryInputDTO.text().text() != null
&& !queryInputDTO.text().text().trim().isEmpty()) {
queryInputBuilder.setText(TextInput.newBuilder()
.setText(queryInputDTO.text().text())
.build());
logger.debug("Mapped text input for QueryInput: '{}'", queryInputDTO.text().text());
} else if (queryInputDTO.event() != null && queryInputDTO.event().event() != null
&& !queryInputDTO.event().event().trim().isEmpty()) {
queryInputBuilder.setEvent(EventInput.newBuilder()
.setEvent(queryInputDTO.event().event())
.build());
logger.debug("Mapped event input for QueryInput: '{}'", queryInputDTO.event().event());
} else {
logger.error("Dialogflow query input (either text or event) is required and must not be empty.");
throw new IllegalArgumentException("Dialogflow query input (either text or event) is required.");
}
QueryParameters.Builder queryParametersBuilder = QueryParameters.newBuilder();
Struct.Builder paramsStructBuilder = Struct.newBuilder();
if (requestDto.queryParams() != null && requestDto.queryParams().parameters() != null) {
for (Map.Entry<String, Object> entry : requestDto.queryParams().parameters().entrySet()) {
Value protobufValue = ProtobufUtil.convertJavaObjectToProtobufValue(entry.getValue());
paramsStructBuilder.putFields(entry.getKey(), protobufValue);
logger.debug("Added session parameter from DTO queryParams: Key='{}', Value='{}'",
entry.getKey(),entry.getValue());
}
}
if (paramsStructBuilder.getFieldsCount() > 0) {
queryParametersBuilder.setParameters(paramsStructBuilder.build());
logger.debug("All custom session parameters added to Protobuf request builder.");
} else {
logger.debug("No custom session parameters to add to Protobuf request.");
}
DetectIntentRequest.Builder detectIntentRequestBuilder = DetectIntentRequest.newBuilder()
.setQueryInput(queryInputBuilder.build());
if (queryParametersBuilder.hasParameters()) {
detectIntentRequestBuilder.setQueryParams(queryParametersBuilder.build());
}
logger.debug("Finished building partial DetectIntentRequest Protobuf Builder.");
return detectIntentRequestBuilder;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.mapper.conversation;
import com.google.cloud.dialogflow.cx.v3.QueryResult;
import com.google.cloud.dialogflow.cx.v3.ResponseMessage;
import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse;
import com.example.dto.dialogflow.base.DetectIntentResponseDTO;
import com.example.dto.dialogflow.conversation.QueryResultDTO;
import com.example.util.ProtobufUtil;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* Spring component responsible for mapping a Dialogflow CX API response
* (`DetectIntentResponse`) to a simplified custom DTO (`DetectIntentResponseDTO`).
* It extracts and consolidates the fulfillment text, and converts Protobuf
* session parameters into standard Java objects, providing a clean and
* decoupled interface for consuming Dialogflow results.
*/
@Component
public class DialogflowResponseMapper {
private static final Logger logger = LoggerFactory.getLogger(DialogflowResponseMapper.class);
public DetectIntentResponseDTO mapFromDialogflowResponse(DetectIntentResponse response, String sessionId) {
logger.info("Starting mapping of Dialogflow DetectIntentResponse for session: {}", sessionId);
String responseId = response.getResponseId();
QueryResult dfQueryResult = response.getQueryResult();
logger.debug("Extracted QueryResult object for session: {}", sessionId);
StringBuilder responseTextBuilder = new StringBuilder();
if (dfQueryResult.getResponseMessagesList().isEmpty()) {
logger.debug("No response messages found in QueryResult for session: {}", sessionId);
}
for (ResponseMessage message : dfQueryResult.getResponseMessagesList()) {
if (message.hasText()) {
logger.debug("Processing text response message for session: {}", sessionId);
for (String text : message.getText().getTextList()) {
if (responseTextBuilder.length() > 0) {
responseTextBuilder.append(" ");
}
responseTextBuilder.append(text);
logger.debug("Appended text segment: '{}' to fulfillment text for session: {}", text, sessionId);
}
} else {
logger.debug("Skipping non-text response message type: {} for session: {}", message.getMessageCase(), sessionId);
}
}
String responseText = responseTextBuilder.toString().trim();
Map<String, Object> parameters = new LinkedHashMap<>(); // Inicializamos vacío para evitar NPEs después
if (dfQueryResult.hasParameters()) {
// Usamos un forEach en lugar de Collectors.toMap para tener control total sobre nulos
dfQueryResult.getParameters().getFieldsMap().forEach((key, value) -> {
try {
Object convertedValue = ProtobufUtil.convertProtobufValueToJavaObject(value);
// Si el valor convertido es nulo, decidimos qué hacer.
// Lo mejor es poner un String vacío o ignorarlo para que no explote tu lógica.
if (convertedValue != null) {
parameters.put(key, convertedValue);
} else {
logger.warn("El parámetro '{}' devolvió un valor nulo al convertir. Se ignorará.", key);
// Opcional: parameters.put(key, "");
}
} catch (Exception e) {
logger.error("Error convirtiendo el parámetro '{}' de Protobuf a Java: {}", key, e.getMessage());
}
});
logger.debug("Extracted parameters: {} for session: {}", parameters, sessionId);
} else {
logger.debug("No parameters found in QueryResult for session: {}. Using empty map.", sessionId);
}
QueryResultDTO ourQueryResult = new QueryResultDTO(responseText, parameters);
logger.debug("Internal QueryResult DTO created for session: {}. Details: {}", sessionId, ourQueryResult);
DetectIntentResponseDTO finalResponse = new DetectIntentResponseDTO(responseId, ourQueryResult);
logger.info("Finished mapping DialogflowDetectIntentResponse for session: {}. Full response ID: {}", sessionId, responseId);
return finalResponse;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.mapper.conversation;
import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
import com.example.dto.dialogflow.conversation.ExternalConvRequestDTO;
import com.example.dto.dialogflow.conversation.QueryInputDTO;
import com.example.dto.dialogflow.conversation.QueryParamsDTO;
import com.example.dto.dialogflow.conversation.TextInputDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Spring component responsible for mapping a simplified, external API request
* into a structured `DetectIntentRequestDTO` for Dialogflow. It processes
* user messages and relevant context, such as phone numbers and channel information,
* and populates the `QueryInputDTO` and `QueryParamsDTO` fields required for
* a Dialogflow API call.
*/
@Component
public class ExternalConvRequestMapper {
private static final Logger logger = LoggerFactory.getLogger(ExternalConvRequestMapper.class);
private static final String DEFAULT_LANGUAGE_CODE = "es";
public DetectIntentRequestDTO mapExternalRequestToDetectIntentRequest(ExternalConvRequestDTO externalRequest) {
Objects.requireNonNull(externalRequest, "ExternalRequestDTO cannot be null for mapping.");
if (externalRequest.message() == null || externalRequest.message().isBlank()) {
throw new IllegalArgumentException("External request 'mensaje' (message) is required.");
}
TextInputDTO textInput = new TextInputDTO(externalRequest.message());
QueryInputDTO queryInputDTO = new QueryInputDTO(textInput,null,DEFAULT_LANGUAGE_CODE);
// 2. Map ALL relevant external fields into QueryParamsDTO.parameters
Map<String, Object> parameters = new HashMap<>();
String primaryPhoneNumber = null;
if (externalRequest.user() != null && externalRequest.user().telefono() != null
&& !externalRequest.user().telefono().isBlank()) {
primaryPhoneNumber = externalRequest.user().telefono();
parameters.put("telefono", primaryPhoneNumber);
}
if (primaryPhoneNumber == null || primaryPhoneNumber.isBlank()) {
throw new IllegalArgumentException(
"Phone number is required in the 'usuario' field for conversation management.");
}
String resolvedUserId = null;
// Derive from phone number if not provided by 'userId' parameter
resolvedUserId = "user_by_phone_" + primaryPhoneNumber.replaceAll("[^0-9]", "");
parameters.put("usuario_id", resolvedUserId); // Ensure derived ID is also in params
logger.warn("User ID not provided in external request. Using derived ID from phone number: {}", resolvedUserId);
if (externalRequest.channel() != null && !externalRequest.channel().isBlank()) {
parameters.put("canal", externalRequest.channel());
logger.debug("Mapped 'canal' from external request: {}", externalRequest.channel());
}
if (externalRequest.user() != null && externalRequest.user().nickname() != null
&& !externalRequest.user().nickname().isBlank()) {
parameters.put("nickname", externalRequest.user().nickname());
logger.debug("Mapped 'nickname' from external request: {}", externalRequest.user().nickname());
}
if (externalRequest.tipo() != null) {
parameters.put("tipo", externalRequest.tipo());
logger.debug("Mapped 'tipo' from external request: {}", externalRequest.tipo());
}
QueryParamsDTO queryParamsDTO = new QueryParamsDTO(parameters);
// 3. Construct the final DetectIntentRequestDTO
return new DetectIntentRequestDTO(queryInputDTO, queryParamsDTO);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.mapper.conversation;
import com.example.dto.dialogflow.conversation.ConversationSessionDTO;
import com.google.cloud.Timestamp;
import com.google.cloud.firestore.DocumentSnapshot;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@Component
public class FirestoreConversationMapper {
public ConversationSessionDTO mapFirestoreDocumentToConversationSessionDTO(DocumentSnapshot document) {
if (document == null || !document.exists()) {
return null;
}
Timestamp createdAtTimestamp = document.getTimestamp("fechaCreacion");
Timestamp lastModifiedTimestamp = document.getTimestamp("ultimaActualizacion");
Instant createdAt = (createdAtTimestamp != null) ? createdAtTimestamp.toDate().toInstant() : null;
Instant lastModified = (lastModifiedTimestamp != null) ? lastModifiedTimestamp.toDate().toInstant() : null;
return new ConversationSessionDTO(
document.getString("sessionId"),
document.getString("userId"),
document.getString("telefono"),
createdAt,
lastModified,
document.getString("ultimoMensaje"),
document.getString("pantallaContexto")
);
}
public Map<String, Object> createSessionMap(ConversationSessionDTO session) {
Map<String, Object> sessionMap = new HashMap<>();
sessionMap.put("sessionId", session.sessionId());
sessionMap.put("userId", session.userId());
sessionMap.put("telefono", session.telefono());
sessionMap.put("fechaCreacion", session.createdAt());
sessionMap.put("ultimaActualizacion", session.lastModified());
sessionMap.put("ultimoMensaje", session.lastMessage());
sessionMap.put("pantallaContexto", session.pantallaContexto());
return sessionMap;
}
}