Form Service: Performance Evaluation Implementation Guide
Hey guys! Let's dive into creating a form service for performance evaluations and getting it integrated smoothly. This article will cover the service setup, filtering, DTOs, and how to use it in your mobile app. We'll keep it casual and make sure everything is crystal clear. This will also cover SEO and make it unique.
Understanding the JSON Data
Before we jump into the code, let's break down the JSON data we're working with. This part is crucial because understanding the data structure helps us design our service and DTOs effectively.
JSON Structure Overview
From the provided data, we can identify four main records. Each record represents a form related to either a "Programa de excelencia" or an "Evaluación de Desempeño" (Performance Evaluation), and it's targeted at either the "Canal Detalle" (Retail Channel) or "Canal Mayoreo" (Wholesale Channel) in El Salvador.
- Programa de excelencia - Canal Detalle (El Salvador)
- Type:
programa_excelencia
- Channel:
detalle
- Type:
- Evaluación de Desempeño en Campo - Canal Mayoreo (El Salvador)
- Type:
evaluacion_desempeƱo
- Channel:
mayoreo
- Type:
- Programa de excelencia - Canal Mayoreo (El Salvador)
- Type:
programa_excelencia
- Channel:
mayoreo
- Type:
- Evaluación de Desempeño en Campo - Canal Detalle (El Salvador)
- Type:
evaluacion_desempeƱo
- Channel:
detalle
- Type:
Summary by Type
To get a clearer picture, let's summarize the forms by type:
- programa_excelencia: 2 forms
- detalle
- mayoreo
- evaluacion_desempeƱo: 2 forms
- detalle
- mayoreo
For our current flow, we'll focus on the evaluacion_desempeƱo
forms. These are the ones we'll use to build our service and integrate into the mobile app.
Key Fields in the JSON
Let's look at the key fields in the JSON data. This will guide our DTO (Data Transfer Object) design and filtering logic.
- id: A unique identifier for the form (String). This is essential for referencing specific forms within our application.
- nombre: The name of the form (String). Helps in identifying the form in a human-readable format.
- version: The version of the form (String). Important for tracking changes and updates.
- canal: The channel the form is designed for (
detalle
ormayoreo
, String). This is a key field for filtering forms based on the channel. - tipo: The type of form (
evaluacion_desempeƱo
orprograma_excelencia
, String). We will use this to filter for performance evaluation forms. - descripcion: A description of the form (String). Provides additional context about the form's purpose.
- preguntas: An array of questions in the form (Array of Objects). Each question has its own structure, which we'll dive into later.
- resultadoKPI: An optional object for KPI results (Object, nullable). We'll handle this in a later phase, but it's good to be aware of it now.
- pais: The country the form is applicable to (String). Useful for filtering forms based on location if needed.
- updatedAt: A timestamp indicating when the form was last updated (Integer). We'll use this to select the most recent version of a form.
- activa: A boolean indicating whether the form is active (Boolean). We'll only use active forms.
Diving into the preguntas
Array
The preguntas
array is a critical part of the form structure. Each question object contains the following key fields:
- name: A unique name for the question (String). Used for referencing the question in the application.
- etiqueta: The label or text of the question (String). This is what the user sees.
- section: The section the question belongs to (String). Useful for organizing questions within the form.
- orden: The order of the question within the section (Integer). Important for displaying questions in the correct sequence.
- tipoEntrada: The input type for the question (
radio
ortext
, String). Determines how the question is rendered in the UI. - ponderacion: The weight or score of the question (Integer, nullable). Used for calculating overall scores.
- opciones: An optional array of options for radio-type questions (Array of Objects, nullable). Each option has a
valor
(value) and apuntuacion
(score).
Example Question Structures
Let's look at a couple of example question structures:
Radio Type Question
{
"etiqueta": "Paso 1: Preparación de la visita (1)",
"ponderacion": 1,
"opciones": [{"valor":"SĆ","puntuacion":1},{"valor":"No","puntuacion":0}],
"name": "preparacion_visita",
"section": "PASOS DE LA VENTA",
"orden": 1,
"tipoEntrada": "radio"
}
This is a radio-type question with two options: "SĆ" (score 1) and "No" (score 0). The ponderacion
is 1, meaning this question has a weight of 1 in the overall score.
Text Type Question
{
"name": "positivo",
"etiqueta": "Retroalimentación positiva (texto)",
"section": "RETROALIMENTACIĆN Y RECONOCIMIENTO",
"orden": 13,
"tipoEntrada": "text"
}
This is a text-type question where the user can enter free-form text. There are no opciones
or ponderacion
in this case.
Why This Matters
Understanding the JSON structure is super important for several reasons:
- DTO Design: We need to create DTOs that accurately represent the data structure. This makes our code type-safe and easier to work with.
- Filtering: We need to filter the forms based on
tipo
,canal
, andactiva
. Knowing the data structure helps us write efficient filtering logic. - Rendering: We need to render the questions dynamically based on
tipoEntrada
. Understanding the question structure allows us to build a flexible rendering component.
By thoroughly understanding the JSON data, we can build a robust and maintainable form service. Now, let's move on to the next part: creating the GitHub issue and outlining the service and filtering requirements.
GitHub Issue: Form Service + Filter by Type and Channel
Now, let's outline the steps to create the service and filter forms. This will help us stay organized and ensure we cover all the necessary points.
Issue Title
š§© Form Service (Performance Evaluation) + Basic Integration
Endpoint Base
.../planes_formularios/
Authentication
Bearer token (same scheme as the rest of the app)
Environment
Don't hardcode the URL! Get the baseUrl
from shared/configuracion/ambiente_config.dart
.
Objective
Create a service that queries the forms endpoint and returns exactly one form, filtered by:
tipo
=evaluacion_desempeƱo
canal
=detalle
|mayoreo
(based on a combo box in the view)
The returned form will be used for dynamic rendering in mobile/vistas/evaluacion_desempeƱo/evaluacion_desempeƱo_llenado.dart
.
Tasks
- New
FormulariosService
inlib/shared/servicios/
:- Construct the URL using
AmbienteConfig
:GET {baseUrl}/planes_formularios/
- Include the Bearer token in the headers.
- Filter client-side by
tipo
andcanal
. - If there are multiple matches, choose the most recent (
updatedAt
) andactiva == true
.
- Construct the URL using
- DTOs in
lib/shared/modelos/
:FormularioDTO { id, nombre, version, canal, tipo, descripcion, preguntas, resultadoKPI?, pais, updatedAt, activa }
PreguntaDTO { name, etiqueta, section, orden, tipoEntrada, ponderacion?, opciones? }
- Public Service Signature:
class FormulariosService {
Future<FormularioDTO> obtenerFormulario({
required String bearerToken,
required String tipo, // 'evaluacion_desempeƱo'
required String canal, // 'detalle' | 'mayoreo'
String? pais, // optional if filtering by country is needed
bool forceRefresh = false,
});
}
- (Optional, Recommended) Cache in Hive:
- Use a key like
form:{tipo}:{canal}:{pais?:*}:{version}
with a short TTL. - Invalidate cache with
forceRefresh
.
- Use a key like
- Minimal Integration in
evaluacion_desempeƱo_llenado.dart
:- Receive the channel from the main screen.
- Call
FormulariosService.obtenerFormulario(tipo:'evaluacion_desempeƱo', canal: canalSel, bearerToken: token)
. - Pass the
FormularioDTO
to the current renderer/builder.
- Initial Type Mapping:
radio
ā Options: Yes/No with weighting (useopciones
).text
āTextFormField
with placeholder + limit 300 (rule already defined).
Acceptance Criteria (QA)
- The service doesn't have hardcoded URLs; it uses
AmbienteConfig
. - The call is made with a Bearer token and handles network errors (timeout, 4xx/5xx).
- For
canal=Detalle
, it returns the form āEvaluación de DesempeƱo en Campo - Canal Detalle (El Salvador)ā. - For
canal=Mayoreo
, it returns the corresponding form for wholesale. - If there are no active matches, it returns a controlled error with a clear message.
- In
evaluacion_desempeƱo_llenado.dart
, the questions from the returned form are rendered, respectingtipoEntrada
.
Snippets Guide (Interface Only; Implementation by Claude)
Service
class FormulariosService {
final AmbienteConfig _env;
final HttpClientLike _http;
FormulariosService(this._env, this._http);
Future<FormularioDTO> obtenerFormulario({
required String bearerToken,
required String tipo,
required String canal,
String? pais,
bool forceRefresh = false,
}) async {
final url = Uri.parse('${_env.apiBaseUrl}/planes_formularios/');
final resp = await _http.get(url, headers: {
'Authorization': 'Bearer $bearerToken',
'Content-Type': 'application/json',
});
final List data = jsonDecode(resp.body) as List;
final candidatos = data.where((m) =>
(m['tipo']?.toString().toLowerCase() == tipo.toLowerCase()) &&
(m['canal']?.toString().toLowerCase() == canal.toLowerCase()) &&
(m['activa'] == true)
);
final seleccionado = candidatos
.sorted((a,b) => (b['updatedAt'] ?? 0).compareTo(a['updatedAt'] ?? 0))
.firstOrNull;
if (seleccionado == null) {
throw StateError('No se encontró formulario activo para tipo=$tipo canal=$canal');
}
return FormularioDTO.fromJson(seleccionado);
}
}
Usage in evaluacion_desempeƱo_llenado.dart
final form = await _formulariosSvc.obtenerFormulario(
bearerToken: token,
tipo: 'evaluacion_desempeƱo',
canal: canalSeleccionado, // 'detalle' | 'mayoreo'
);
// Dynamic rendering
FormRenderer(schema: form); // where schema uses form.preguntas
Note
For text
, apply the previously agreed-upon validation (max. 300 characters).
Prompt for Claude Code ā Form Service + Integration in āPerformance Evaluation (Fill)ā
Objective
Implement a service that queries the forms endpoint and returns the active form filtered by tipo = evaluacion_desempeƱo
and canal = detalle|mayoreo
, and use it for dynamic rendering in evaluacion_desempeƱo_llenado.dart
.
Rules and Restrictions
- Don't hardcode URLs. Take the base URL from
shared/configuracion/ambiente_config.dart
. - Auth: Bearer token (same pattern as existing services).
- If there are multiple matches, choose the active one (
activa == true
) with the highestupdatedAt
. - Handle network/parsing errors with visible messages and an option to retry.
Files to Create/Modify
-
New Service:
lib/shared/servicios/formularios_service.dart
-
DTOs (Models):
lib/shared/modelos/formulario_dto.dart
lib/shared/modelos/pregunta_dto.dart
(or another name consistent with the project)
-
View Integration (Fill):
package:diana_lc_front/mobile/vistas/evaluacion_desempeƱo/evaluacion_desempeƱo_llenado.dart
The main screen already passes the selected channel. In the fill view, use it to request the form.
Contracts / Expected Signatures
Service
class FormulariosService {
final AmbienteConfig _env; // Inject the environment config
final HttpClientLike _httpClient; // Use the existing client from the project
FormulariosService(this._env, this._httpClient);
Future<FormularioDTO> obtenerFormulario({
required String bearerToken,
required String tipo, // 'evaluacion_desempeƱo'
required String canal, // 'detalle' | 'mayoreo'
String? pais, // optional in this phase
bool forceRefresh = false,
});
}
Minimum DTOs
class FormularioDTO {
final String id;
final String nombre;
final String version;
final String canal; // 'detalle' | 'mayoreo'
final String tipo; // 'evaluacion_desempeƱo'
final String? descripcion;
final bool activa;
final int? updatedAt;
final String? pais;
final List<PreguntaDTO> preguntas;
final Map<String, dynamic>? resultadoKPI;
// fromJson / toJson
}
class PreguntaDTO {
final String name;
final String etiqueta;
final String section;
final int orden;
final String tipoEntrada; // 'radio' | 'text' | ...
final int? ponderacion;
final List<OpcionDTO>? opciones;
// fromJson / toJson
}
class OpcionDTO {
final String valor;
final num? puntuacion;
// fromJson / toJson
}
Requested Implementation
A) FormulariosService
- Construct URL:
${_env.apiBaseUrl}/planes_formularios/
- GET with Headers:
Authorization: Bearer {token}
Content-Type: application/json
- Parse the returned list.
- Filter:
registro['tipo'] == 'evaluacion_desempeƱo'
registro['canal']
matchescanal
(case-insensitive).registro['activa'] == true
- Select the one with the highest
updatedAt
(orcreatedAt
if missing). - Map to
FormularioDTO
(and itspreguntaDTO
/opcionDTO
).
B) Integration in evaluacion_desempeƱo_llenado.dart
-
Receive via
ModalRoute.of(context)!.settings.arguments
:canal
('Detalle'
|'Mayoreo'
). Normalize to lower:'detalle'|'mayoreo'
.- (In addition to
liderId
,asesorId
, etc., which are already passed).
-
Get the current token (same method as the rest of the app).
-
Call:
final form = await _formulariosService.obtenerFormulario( bearerToken: token, tipo: 'evaluacion_desempeƱo', canal: canalNormalizado, );
-
Dynamically render all questions from
form.preguntas
:radio
ā construct options usingopciones[].valor
and, if applicable, preservepuntuacion
.text
āTextFormField
with placeholder ā(max. 300 characters)ā and limit 300 (maxLength
,MaxLengthEnforcement.enforced
,LengthLimitingTextInputFormatter(300)
), as previously agreed upon.
-
Loading + Error:
- While loading ā
CircularProgress
/ skeleton. - Error ā card with message and Retry button that triggers the load again.
- While loading ā
Snippets Guide (You can adjust to the project architecture)
Filter and Selection
final List<dynamic> data = jsonDecode(resp.body) as List<dynamic>;
final candidatos = data.where((m) =>
(m['tipo']?.toString().toLowerCase() == tipo.toLowerCase()) &&
(m['canal']?.toString().toLowerCase() == canal.toLowerCase()) &&
(m['activa'] == true)
);
if (candidatos.isEmpty()) {
throw StateError('No hay formulario activo para tipo=$tipo canal=$canal');
}
candidatos.toList().sort((a, b) {
final ua = (a['updatedAt'] ?? a['createdAt'] ?? 0) as int;
final ub = (b['updatedAt'] ?? b['createdAt'] ?? 0) as int;
return ub.compareTo(ua); // desc
});
final seleccionado = candidatos.first;
return FormularioDTO.fromJson(seleccionado as Map<String, dynamic>);
Usage in the View (Summary)
late Future<FormularioDTO> _futureForm;
@override
void initState() {
super.initState();
final args = ModalRoute.of(context)!.settings.arguments as Map?;
final canalArg = (args?['canal'] as String?) ?? 'Detalle';
final canal = canalArg.toLowerCase(); // detalle | mayoreo
_futureForm = _formulariosService.obtenerFormulario(
bearerToken: tokenActual,
tipo: 'evaluacion_desempeƱo',
canal: canal,
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<FormularioDTO>(
future: _futureForm,
builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
}
if (snap.hasError) {
return ErrorCard(
message: 'No fue posible cargar el formulario',
onRetry: () => setState(() {}),
);
}
final form = snap.data!;
return FormRenderer(schema: form); // use your current renderer/builder
},
);
}
Tests / QA (Acceptance Criteria)
- The service constructs the URL with
AmbienteConfig
(no hardcoding). - The request uses a Bearer token and handles 4xx/5xx errors with a message and retry.
- With
canal = Detalle
, it returns the form āEvaluación de DesempeƱo en Campo - Canal Detalle ā¦ā. - With
canal = Mayoreo
, it returns the corresponding form for wholesale. - If there is more than one, the most recent (
updatedAt
) is used. - In
evaluacion_desempeƱo_llenado.dart
, all questions are rendered:radio
with its options.text
with placeholder and limit 300 (typing and pasting).
- The screen shows loading; on error, it shows a message and a retry button.
Note
Next phase (don't implement yet): we'll connect saving responses (autosave/draft/finished) and KPI calculation using resultadoKPI
from the form when applicable.
š§ Rules for Selection (Source of Truth = API)
To ensure our service works correctly, we need to follow specific rules when selecting forms from the API response. These rules are crucial for filtering and choosing the right form.
- Type: The
tipo
field must be exactlyevaluacion_desempeƱo
. This ensures we are only fetching performance evaluation forms. - Channel: The
canal
field should be eitherdetalle
(retail) ormayoreo
(wholesale). This value will come from the UI, so we need to compare it case-insensitive to handle variations in input. - Active: The
activa
field must betrue
. We only want to use forms that are currently active. - Multiple Matches: If we find multiple forms that match the criteria, we need to select the most recent one. This means choosing the form with the highest
updatedAt
value. IfupdatedAt
is not available, we can fallback to usingcreatedAt
. - URL Base: The base URL for the API endpoint should be taken from
AmbienteConfig
. This ensures we are using the correct environment (e.g., development, staging, production) and avoids hardcoding URLs. - Endpoint: The specific endpoint we are targeting is
/planes_formularios/
. - Authentication: We need to include a Bearer token in the request headers for authentication. This follows the same pattern used by other services in the app.
šÆ Why Include This Snippet
Including a detailed snippet like this in the issue is beneficial for several reasons:
- Contract Definition: It provides Claude (or any developer) with a clear contract for the data structure, including field names, types, and nesting. This eliminates ambiguity and ensures everyone is on the same page.
- DTO Design: The snippet helps in designing DTOs (Data Transfer Objects) that accurately represent the data. This makes the code type-safe and easier to maintain.
- Mapping and Validations: With a clear understanding of the data structure, it's easier to design mapping logic and validation rules.
- Lightweight Issue: By using
<details>
to hide the full JSON sample, we keep the issue lightweight and easy to navigate. The relevant information is still available, but it doesn't clutter the main issue description.
By providing these rules and snippets, we set a solid foundation for implementing the form service. This approach minimizes misunderstandings and helps ensure a smooth development process.
Creating the FormulariosService
Alright, let's get into the nitty-gritty of building the FormulariosService
. This service will be responsible for fetching, filtering, and returning the correct form based on our criteria.
Service Structure
First, let's outline the structure of our service. We'll need to inject the AmbienteConfig
and an HTTP client. This allows us to construct the URL dynamically and make the API request.
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../modelos/formulario_dto.dart';
import '../configuracion/ambiente_config.dart';
class FormulariosService {
final AmbienteConfig _env;
final http.Client _httpClient;
FormulariosService(this._env, this._httpClient);
Future<FormularioDTO> obtenerFormulario({
required String bearerToken,
required String tipo,
required String canal,
String? pais,
bool forceRefresh = false,
}) async {
// Implementation here
}
}
Constructing the URL
The first step in our obtenerFormulario
method is to construct the URL. We'll use the baseUrl
from AmbienteConfig
and append the /planes_formularios/
endpoint.
final url = Uri.parse('${_env.apiBaseUrl}/planes_formularios/');
Making the API Request
Next, we'll make a GET
request to the API endpoint. We need to include the Bearer token in the Authorization
header and set the Content-Type
to application/json
.
final response = await _httpClient.get(
url,
headers: {
'Authorization': 'Bearer $bearerToken',
'Content-Type': 'application/json',
},
);
if (response.statusCode < 200 || response.statusCode >= 300) {
throw Exception('Failed to load form: ${response.statusCode}');
}
Parsing the Response
Once we have the response, we need to parse the JSON data. We'll use jsonDecode
to convert the response body into a list of dynamic objects.
final List<dynamic> data = jsonDecode(response.body) as List<dynamic>;
Filtering the Forms
Now comes the crucial part: filtering the forms based on our rules. We need to filter by tipo
, canal
, and activa
.
final candidatos = data.where((m) =>
(m['tipo']?.toString().toLowerCase() == tipo.toLowerCase()) &&
(m['canal']?.toString().toLowerCase() == canal.toLowerCase()) &&
(m['activa'] == true)).toList();
This code snippet filters the list based on the following conditions:
m['tipo']
must be equal to the providedtipo
(case-insensitive).m['canal']
must be equal to the providedcanal
(case-insensitive).m['activa']
must betrue
.
Selecting the Most Recent Form
If we have multiple matching forms, we need to select the most recent one. We'll sort the forms by updatedAt
in descending order and take the first element.
candidatos.sort((a, b) {
final ua = (a['updatedAt'] ?? a['createdAt'] ?? 0) as int;
final ub = (b['updatedAt'] ?? b['createdAt'] ?? 0) as int;
return ub.compareTo(ua); // Descending order
});
final seleccionado = candidatos.isNotEmpty ? candidatos.first : null;
Handling No Matching Forms
If we don't find any matching forms, we should throw an error with a clear message.
if (seleccionado == null) {
throw StateError('No active form found for type=$tipo and channel=$canal');
}
Mapping to FormularioDTO
Finally, we need to map the selected form data to our FormularioDTO
. This involves creating an instance of FormularioDTO
and populating it with the data from the JSON object.
return FormularioDTO.fromJson(seleccionado as Map<String, dynamic>);
Complete obtenerFormulario
Method
Here's the complete obtenerFormulario
method:
Future<FormularioDTO> obtenerFormulario({
required String bearerToken,
required String tipo,
required String canal,
String? pais,
bool forceRefresh = false,
}) async {
final url = Uri.parse('${_env.apiBaseUrl}/planes_formularios/');
final response = await _httpClient.get(
url,
headers: {
'Authorization': 'Bearer $bearerToken',
'Content-Type': 'application/json',
},
);
if (response.statusCode < 200 || response.statusCode >= 300) {
throw Exception('Failed to load form: ${response.statusCode}');
}
final List<dynamic> data = jsonDecode(response.body) as List<dynamic>;
final candidatos = data.where((m) =>
(m['tipo']?.toString().toLowerCase() == tipo.toLowerCase()) &&
(m['canal']?.toString().toLowerCase() == canal.toLowerCase()) &&
(m['activa'] == true)).toList();
candidatos.sort((a, b) {
final ua = (a['updatedAt'] ?? a['createdAt'] ?? 0) as int;
final ub = (b['updatedAt'] ?? b['createdAt'] ?? 0) as int;
return ub.compareTo(ua); // Descending order
});
final seleccionado = candidatos.isNotEmpty ? candidatos.first : null;
if (seleccionado == null) {
throw StateError('No active form found for type=$tipo and channel=$canal');
}
return FormularioDTO.fromJson(seleccionado as Map<String, dynamic>);
}
Caching (Optional)
For better performance, we can implement caching. We'll use Hive to cache the forms with a short TTL (Time-To-Live). The cache key will be based on tipo
, canal
, pais
, and version
.
Integration in evaluacion_desempeƱo_llenado.dart
Now that we have our FormulariosService
, let's see how we can integrate it into evaluacion_desempeƱo_llenado.dart
. This is where we'll use the service to fetch the form and render it dynamically.
Receiving the Channel
First, we need to receive the channel from the main screen. We can do this by accessing the ModalRoute
arguments.
final args = ModalRoute.of(context)!.settings.arguments as Map?;
final canalArg = (args?['canal'] as String?) ?? 'Detalle';
final canal = canalArg.toLowerCase(); // Normalize to 'detalle' or 'mayoreo'
Fetching the Token
Next, we need to get the current token. This will depend on how your app manages authentication. Assuming you have a method to get the token, you can call it here.
final token = await getToken(); // Replace with your actual token retrieval method
Calling the Service
Now, we can call our FormulariosService
to fetch the form. We'll pass the bearerToken
, tipo
, and canal
.
final form = await _formulariosService.obtenerFormulario(
bearerToken: token,
tipo: 'evaluacion_desempeƱo',
canal: canal,
);
Rendering the Form
Once we have the form
, we can render it dynamically. This involves iterating over the form.preguntas
list and creating widgets based on the tipoEntrada
.
FormRenderer(schema: form);
Handling Loading and Errors
It's essential to handle loading and error states. While the form is loading, we can show a CircularProgressIndicator
. If there's an error, we can display an error message with a retry button.
late Future<FormularioDTO> _futureForm;
@override
void initState() {
super.initState();
final args = ModalRoute.of(context)!.settings.arguments as Map?;
final canalArg = (args?['canal'] as String?) ?? 'Detalle';
final canal = canalArg.toLowerCase(); // detalle | mayoreo
_futureForm = _formulariosService.obtenerFormulario(
bearerToken: tokenActual,
tipo: 'evaluacion_desempeƱo',
canal: canal,
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<FormularioDTO>(
future: _futureForm,
builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
}
if (snap.hasError) {
return ErrorCard(
message: 'Failed to load form',
onRetry: () => setState(() {}),
);
}
final form = snap.data!;
return FormRenderer(schema: form); // Use your form renderer
},
);
}
Mapping Input Types
We need to map the tipoEntrada
values to the appropriate widgets.
radio
ā Construct options usingopciones[].valor
and preservepuntuacion
.text
āTextFormField
with placeholder ā(max. 300 characters)ā and limit 300.
Acceptance Criteria and QA
To ensure our service and integration meet the requirements, we need to verify the following:
- The service constructs the URL using
AmbienteConfig
(no hardcoding). - The request uses a Bearer token and handles 4xx/5xx errors with a message and retry.
- With
canal = Detalle
, it returns the correct form. - With
canal = Mayoreo
, it returns the correct form. - If there is more than one matching form, the most recent one (
updatedAt
) is used. - In
evaluacion_desempeƱo_llenado.dart
, all questions are rendered correctly:radio
questions with their options.text
questions with the placeholder and 300-character limit.
- The screen displays a loading indicator while the form is being fetched.
- If there's an error, a message and retry button are displayed.
SEO Optimization
To optimize this article for SEO, we've included relevant keywords throughout the text, such as "form service," "performance evaluation," "DTOs," "API integration," and "dynamic rendering." We've also used headings and subheadings to structure the content logically, making it easier for search engines to understand the article's main points.
Conclusion
Creating a form service and integrating it into a mobile app can seem daunting, but by breaking it down into smaller tasks, it becomes much more manageable. We've covered everything from understanding the JSON data to building the service, integrating it into the view, and handling loading and error states. Remember, clear communication and a solid understanding of the requirements are key to success. Keep coding, and you'll nail it!